- Backend calculates password age based on
PasswordUpdatedAttimestamp - Warning shown when:
(Password_expiration_duration - password_age) <= Password_expiration_warning - Frontend displays dialog with human-readable time (days/hours)
- Users can choose "Change Password Now" or "Remind Me Later"
- Dedicated ChangePassword component with validation
- Backend endpoint validates current password before allowing change
- Enforces password complexity requirements
- Prevents reusing the same password
- Audit trail logging for password changes
- Invalid login attempts tracked per user
- Account locks after exceeding allowed attempts
- Automatic unlock after lockout period expires
- Manual unlock available for administrators
{
"Login_Inactivity_Timeout": 300, // 5 minutes
"Invalid_Login_Attempts_Allowed": 3, // 3 attempts
"Invalid_Login_Lockout_Period": 14400, // 4 hours
"Password_expiration_duration": 2592000, // 30 days
"Password_expiration_warning": 604800 // 7 days
}- Minimum 8 characters
- At least one uppercase letter (A-Z)
- At least one lowercase letter (a-z)
- At least one number (0-9)
- At least one special character (@$!%*?&)
Objective: Verify normal login flow when password is not near expiration
Preconditions:
- User exists with valid credentials
- Password age < (Password_expiration_duration - Password_expiration_warning)
- Account not locked
Steps:
- Navigate to login page
- Enter valid email and password
- Click "Sign In"
- Enter OTP received via email
- Click "Verify OTP"
Expected Result:
- ✅ OTP sent successfully
- ✅ User redirected to home page
- ✅ No password expiration warning shown
- ✅ Inactivity timeout set correctly
Database Check:
// Password age calculation
const passwordAge = Math.floor(Date.now()/1000) - user.PasswordUpdatedAt;
const daysUntilExpiry = Password_expiration_duration - passwordAge;
// Should be > Password_expiration_warning (604800 seconds / 7 days)Objective: Verify warning dialog appears when password is near expiration
Preconditions:
- User exists with valid credentials
- Password age = Password_expiration_duration - Password_expiration_warning (or less)
- Example: For 30-day expiration with 7-day warning, password should be 23+ days old
Test Data Setup:
// Manually update user in Cosmos DB to simulate old password
{
"PasswordUpdatedAt": Math.floor(Date.now()/1000) - 2000000 // ~23 days ago
}Steps:
- Navigate to login page
- Enter valid email and password
- Click "Sign In"
- Enter OTP received via email
- Click "Verify OTP"
Expected Result:
- ✅ Password warning dialog appears
- ✅ Dialog shows: "Your password will expire in X days and Y hours"
- ✅ Two buttons: "Change Password Now" and "Remind Me Later"
- ✅ Calculation accurate (converts seconds to days/hours)
Calculation Verification:
// Frontend calculation in Loginpage.js
const seconds = res.data.data.daysUntilExpiry; // From backend
const days = Math.floor(seconds / 86400);
const hours = Math.floor((seconds % 86400) / 3600);
// Example: 592800 seconds = 6 days, 21 hoursOption A - "Remind Me Later":
- ✅ Dialog closes
- ✅ User redirected to home page
- ✅ Can continue using application normally
- ✅ Warning will appear on next login
Option B - "Change Password Now":
- ✅ Dialog closes
- ✅ User redirected to
/changepasswordpage - ✅ Email field pre-filled and disabled
Objective: Verify login blocked when password has expired
Test Data Setup:
// Update user in Cosmos DB
{
"PasswordUpdatedAt": Math.floor(Date.now()/1000) - 2700000, // ~31 days ago
"PasswordExpired": true
}Steps:
- Navigate to login page
- Enter valid email and password (even if correct)
- Click "Sign In"
- Enter OTP
- Click "Verify OTP"
Expected Result:
- ✅ OTP verification fails
- ✅ Error message: "Your password has expired"
- ✅ User NOT logged in
- ✅ Must use "Forgot Password" flow to reset
Backend Response:
{
state: 0,
username: "",
userdetails: {},
message: "Password Expired"
}Objective: Verify user can successfully change password
Preconditions:
- User logged in (has valid session token)
- Knows current password
Steps:
- Navigate to
/changepassword(from warning dialog or manually) - Email field should be pre-filled and disabled
- Enter current password:
CurrentPass123! - Enter new password:
NewSecurePass456@ - Enter confirm password:
NewSecurePass456@ - Click "Change Password"
Expected Result:
- ✅ Backend validates current password
- ✅ Backend checks password complexity
- ✅
PasswordUpdatedAttimestamp updated in database - ✅
PasswordExpiredflag set tofalse - ✅ Success dialog: "Password changed successfully! Please login with your new password."
- ✅ User redirected to login page
- ✅ Session cleared
- ✅ Audit trail logged
Database Changes:
// User document after successful change
{
"Password": "NewSecurePass456@", // New password
"PasswordUpdatedAt": 1734774000, // Current timestamp
"PasswordExpired": false,
"CurrentInvalidLoginAttemptCount": 0 // Reset counter
}Steps:
- New password:
NewPass123! - Confirm password:
DifferentPass456@
Expected Result:
- ✅ Error: "New password and confirm password do not match"
- ✅ Form not submitted
Steps:
- Current password:
CurrentPass123! - New password:
CurrentPass123!
Expected Result:
- ✅ Error: "New password must be different from current password"
- ✅ Backend rejects the request
Steps:
- New password:
weakpass123!
Expected Result:
- ✅ Error: "Password must be at least 8 characters long and contain uppercase, lowercase, number & special character"
Steps:
- New password:
WeakPass123
Expected Result:
- ✅ Same complexity error as above
Steps:
- Current password:
WrongPassword123! - New password:
NewPass456@
Expected Result:
- ✅ Backend error: "Current password is incorrect"
- ✅ HTTP 401 status
Objective: Verify account locks after exceeding invalid attempts
Configuration:
Invalid_Login_Attempts_Allowed: 3Invalid_Login_Lockout_Period: 14400 seconds (4 hours)
Steps:
- Attempt login with wrong password (Attempt 1)
- Attempt login with wrong password (Attempt 2)
- Attempt login with wrong password (Attempt 3)
- Attempt login with correct password (Attempt 4)
Expected Results:
After Attempt 1:
- ✅ Error: "Invalid email or password"
- ✅
CurrentInvalidLoginAttemptCount: 1 - ✅
LastInvalidLoginAttempt: [current timestamp]
After Attempt 2:
- ✅ Error: "Invalid email or password"
- ✅
CurrentInvalidLoginAttemptCount: 2
After Attempt 3:
- ✅ Error: "Invalid email or password"
- ✅
CurrentInvalidLoginAttemptCount: 3 - ✅
UserAccountDisabled: true - ✅
AccountLockedAt: [current timestamp]
After Attempt 4 (Even with Correct Password):
- ✅ Error: "User Account is Disabled. Your account will be unlocked at [time]"
- ✅ Login blocked even with correct credentials
- ✅ Shows exact unlock time formatted as HH:MM:SS
Database State After Lockout:
{
"CurrentInvalidLoginAttemptCount": 3,
"LastInvalidLoginAttempt": 1734770000,
"UserAccountDisabled": true,
"AccountLockedAt": 1734770000
}Objective: Verify account automatically unlocks after lockout period expires
Preconditions:
- Account locked (UserAccountDisabled: true)
- AccountLockedAt timestamp set
- Wait for lockout period to expire (or manually adjust timestamp)
Test Data Manipulation:
// Set lockout time to 5 hours ago (past the 4-hour lockout period)
{
"AccountLockedAt": Math.floor(Date.now()/1000) - 18000 // 5 hours ago
}Steps:
- Wait for lockout period to expire (or use test data above)
- Attempt login with correct credentials
- Enter OTP
Expected Result:
- ✅ Backend checks:
(current_time - AccountLockedAt) > Invalid_Login_Lockout_Period - ✅ Account automatically unlocked
- ✅
UserAccountDisabledset tofalse - ✅
CurrentInvalidLoginAttemptCountreset to 0 - ✅ Login proceeds normally
- ✅ User redirected to home page
Database State After Auto-Unlock:
{
"CurrentInvalidLoginAttemptCount": 0,
"LastInvalidLoginAttempt": 1734770000,
"UserAccountDisabled": false,
"AccountLockedAt": null // or previous value, doesn't matter
}Objective: Verify counter resets after successful login
Preconditions:
- User has 2 invalid attempts (CurrentInvalidLoginAttemptCount: 2)
- Not yet locked
Steps:
- Login with correct credentials
- Enter OTP
- Verify OTP
Expected Result:
- ✅ Login successful
- ✅
CurrentInvalidLoginAttemptCountreset to 0 - ✅ No lockout occurs
- ✅ Counter starts fresh for future attempts
Objective: Verify users can reset expired passwords via email
Preconditions:
- Password expired
- Cannot login normally
Steps:
- Click "Forgot Password?" on login page
- Enter email address
- Click "Submit"
- Check email for reset link
- Click link (redirects to
/reset-password) - Enter new password
- Confirm new password
- Click "Reset Password"
Expected Result:
- ✅ Reset link email sent
- ✅ Reset page loads
- ✅ Password complexity validated
- ✅
PasswordUpdatedAtupdated - ✅
PasswordExpiredset to false - ✅ Success message shown
- ✅ Can now login with new password
// Test 1: Valid credentials, password not expiring
describe('checkuserdetails - Valid Login', () => {
it('should return user details when credentials are correct', async () => {
const mockUser = {
Email: 'test@example.com',
Password: 'ValidPass123!',
PasswordUpdatedAt: Math.floor(Date.now()/1000) - 86400, // 1 day old
UserAccountDisabled: false,
PasswordExpired: false
};
// Mock Cosmos DB query
const result = await checkuserdetails('test@example.com', 'ValidPass123!');
expect(result.state).toBe(1);
expect(result.username).toBe('test@example.com');
expect(result.passwordWarning).toBeUndefined();
});
});
// Test 2: Password near expiration
describe('checkuserdetails - Password Warning', () => {
it('should return password warning when near expiration', async () => {
const mockUser = {
Email: 'test@example.com',
Password: 'ValidPass123!',
PasswordUpdatedAt: Math.floor(Date.now()/1000) - 2000000, // 23 days old
UserAccountDisabled: false,
PasswordExpired: false
};
const result = await checkuserdetails('test@example.com', 'ValidPass123!');
expect(result.state).toBe(1);
expect(result.passwordWarning).toBe(true);
expect(result.daysUntilExpiry).toBeGreaterThan(0);
expect(result.daysUntilExpiry).toBeLessThanOrEqual(604800); // Within warning period
});
});
// Test 3: Expired password
describe('checkuserdetails - Password Expired', () => {
it('should block login when password expired', async () => {
const mockUser = {
Email: 'test@example.com',
Password: 'ValidPass123!',
PasswordUpdatedAt: Math.floor(Date.now()/1000) - 2700000, // 31 days old
UserAccountDisabled: false,
PasswordExpired: true
};
const result = await checkuserdetails('test@example.com', 'ValidPass123!');
expect(result.state).toBe(0);
expect(result.message).toBe('Password Expired');
});
});
// Test 4: Invalid password increments counter
describe('checkuserdetails - Invalid Password', () => {
it('should increment invalid login counter', async () => {
const mockUser = {
Email: 'test@example.com',
Password: 'ValidPass123!',
CurrentInvalidLoginAttemptCount: 1
};
const result = await checkuserdetails('test@example.com', 'WrongPassword!');
expect(result.state).toBe(0);
expect(result.message).toContain('Invalid email or password');
// Verify DB updated: CurrentInvalidLoginAttemptCount should be 2
});
});
// Test 5: Account locked
describe('checkuserdetails - Account Locked', () => {
it('should block login when account is locked', async () => {
const mockUser = {
Email: 'test@example.com',
Password: 'ValidPass123!',
UserAccountDisabled: true,
AccountLockedAt: Math.floor(Date.now()/1000) - 3600, // 1 hour ago
CurrentInvalidLoginAttemptCount: 3
};
const result = await checkuserdetails('test@example.com', 'ValidPass123!');
expect(result.state).toBe(0);
expect(result.message).toContain('User Account is Disabled');
});
});
// Test 6: Account auto-unlock
describe('checkuserdetails - Auto Unlock', () => {
it('should automatically unlock account after lockout period', async () => {
const mockUser = {
Email: 'test@example.com',
Password: 'ValidPass123!',
UserAccountDisabled: true,
AccountLockedAt: Math.floor(Date.now()/1000) - 18000, // 5 hours ago (past 4-hour lockout)
CurrentInvalidLoginAttemptCount: 3
};
const result = await checkuserdetails('test@example.com', 'ValidPass123!');
expect(result.state).toBe(1);
// Verify DB updated: UserAccountDisabled = false, CurrentInvalidLoginAttemptCount = 0
});
});- Backend server running
- Frontend development server running
- Access to Cosmos DB to modify test data
- Email service configured for OTP delivery
- Test user accounts created
- Test 1: Normal login (no warning)
- Test 2: Password warning appears (7 days before expiry)
- Test 3: Warning calculation accurate (days/hours correct)
- Test 4: "Remind Me Later" continues to home
- Test 5: "Change Password Now" navigates to change page
- Test 6: Expired password blocks login
- Test 7: Error message displays correctly
- Test 8: Can reset via "Forgot Password"
- Test 9: Email pre-filled and disabled
- Test 10: Current password validated
- Test 11: Password mismatch error
- Test 12: Same password error
- Test 13: Weak password error (no uppercase)
- Test 14: Weak password error (no special char)
- Test 15: Successful password change
- Test 16: Redirected to login after change
- Test 17: Can login with new password
- Test 18: Cannot login with old password
- Test 19: Invalid attempt counter increments
- Test 20: Account locks after 3 invalid attempts
- Test 21: Cannot login even with correct password when locked
- Test 22: Lockout message shows unlock time
- Test 23: Account auto-unlocks after lockout period
- Test 24: Successful login resets counter
- PasswordUpdatedAt updates on password change
- PasswordExpired flag resets on password change
- CurrentInvalidLoginAttemptCount increments/resets correctly
- UserAccountDisabled toggles correctly
- AccountLockedAt timestamp set correctly
- LastInvalidLoginAttempt timestamp updates
- Password change logged to audit trail
- Login/logout events logged
- User ID, Project ID, Org ID captured correctly
- Timestamps accurate
- Concurrent Logins: 100 users login simultaneously
- OTP Generation: Verify email service handles load
- Password Expiration Check: Database query performance
- Account Lockout: Multiple failed attempts from different users
- Login response time: < 2 seconds
- OTP delivery: < 30 seconds
- Password change: < 1 second
- Database query time: < 500ms
- Attempt SQL injection in email field
- Attempt XSS in password fields
- Brute force password guessing (should trigger lockout)
- Session hijacking attempts
- JWT token tampering
- CSRF attack prevention
- Rate limiting on login endpoint
- Passwords not stored in plain text (should be hashed)
- Audit trail does not log actual passwords (shows *****)
- Console logs do not expose sensitive data in production
Possible Causes:
PasswordUpdatedAttimestamp incorrect in databasePassword_expiration_warningsetting too low- Frontend calculation error
- Backend not returning
passwordWarningflag
Debug Steps:
// Backend console.log in userdatabase.js
console.log("Password Age:", passwordAge);
console.log("Days Until Expiry:", daysUntilExpiry);
console.log("Warning Threshold:", Password_expiration_warning);
// Frontend console.log in Loginpage.js
console.log("Response data:", res.data.data);
console.log("Password Warning:", res.data.data.passwordWarning);
console.log("Days Until Expiry (seconds):", res.data.data.daysUntilExpiry);Possible Causes:
Invalid_Login_Attempts_Allowedsetting incorrect- Counter not incrementing in database
- Logic error in
checkuserdetails()function
Debug Steps:
// Check user document in Cosmos DB
{
"CurrentInvalidLoginAttemptCount": ?, // Should increment
"LastInvalidLoginAttempt": ?, // Should update
"UserAccountDisabled": ?, // Should become true
"AccountLockedAt": ? // Should set timestamp
}Possible Causes:
- Backend endpoint not reachable
- Current password validation failing
- Token not passed correctly
- Database update failing
Debug Steps:
// Check network tab in browser DevTools
// POST /login/changePassword
// Request payload should contain: email, currentPassword, newPassword
// Backend logs
logger.info("Change password request for:", email);
logger.info("Current password valid:", userCheck.state);
logger.info("Update result:", isUpdated);This comprehensive testing guide covers all aspects of the password expiration and change flow. Follow each scenario systematically to ensure the feature works correctly in all edge cases.
✅ Users receive timely warnings before password expiration
✅ Expired passwords block login appropriately
✅ Change password flow is secure and user-friendly
✅ Account lockout prevents brute force attacks
✅ Auto-unlock works after lockout period
✅ Audit trail captures all password-related events
✅ No sensitive data exposed in logs or client-side
- Execute all manual test scenarios
- Write automated tests for backend functions
- Set up monitoring for password expiration events
- Create user documentation for password policies
- Schedule periodic security audits