Changing Password
This guide covers how to change your password, password requirements, and account security best practices.
Overview
When to Change Your Password
Change your password in these situations:
| Situation | Priority | Action |
|---|---|---|
| First Login | Required | Must change temporary password |
| Security Concern | High | Change immediately |
| Regular Rotation | Medium | Every 90 days recommended |
| Shared Device | High | Change after use |
Password Change Flow
Enter Current Password → Verify → Enter New Password → Confirm → Update
Changing Your Password
Access Password Change
- Navigate to My Profile in Staff Portal
- Click Change Password or Security
- The password change form appears
Change Password API
Endpoint: POST /api/auth/change-password
// Source: src/app/api/auth/change-password/route.ts:15-127
const changePasswordSchema = {
currentPassword: 'string (required)',
newPassword: 'string (required)'
}
Request Format
// Change password request
const changePassword = async (currentPassword: string, newPassword: string) => {
const response = await fetch('/api/auth/change-password', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
currentPassword,
newPassword
})
})
return response.json()
}
Success Response
// Successful password change
{
success: true,
message: "Password updated successfully"
}
Password Requirements
Minimum Requirements
| Requirement | Minimum | Recommended |
|---|---|---|
| Length | 8 characters | 12+ characters |
| Uppercase | 1 letter | 2+ letters |
| Lowercase | 1 letter | 2+ letters |
| Numbers | 1 digit | 2+ digits |
| Special Characters | 1 symbol | 2+ symbols |
Valid Special Characters
! @ # $ % ^ & * ( ) - _ = + [ ] { } ; : ' " , . < > / ? \ |
Password Strength Indicators
| Strength | Criteria | Colour |
|---|---|---|
| Weak | < 8 chars or missing requirements | 🔴 Red |
| Fair | 8-10 chars, meets basic requirements | 🟡 Yellow |
| Good | 11-14 chars, exceeds requirements | 🟢 Green |
| Strong | 15+ chars, complex mix | 🔵 Blue |
Password Validation
// Password validation function
const validatePassword = (password: string): ValidationResult => {
const errors: string[] = []
if (password.length < 8) {
errors.push('Password must be at least 8 characters')
}
if (!/[A-Z]/.test(password)) {
errors.push('Password must contain at least one uppercase letter')
}
if (!/[a-z]/.test(password)) {
errors.push('Password must contain at least one lowercase letter')
}
if (!/\d/.test(password)) {
errors.push('Password must contain at least one number')
}
if (!/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password)) {
errors.push('Password must contain at least one special character')
}
return {
valid: errors.length === 0,
errors,
strength: calculateStrength(password)
}
}
Password Change Process
Step 1: Enter Current Password
// Source: src/app/api/auth/change-password/route.ts:52-59
// Request body parsing
const { currentPassword, newPassword } = await request.json()
if (!currentPassword || !newPassword) {
return NextResponse.json(
{ error: 'Current password and new password are required' },
{ status: 400 }
)
}
Step 2: Verify Current Password
// Source: src/app/api/auth/change-password/route.ts:64-82
// Verify current password by attempting to sign in
const { error: verifyError } = await verifierClient.auth.signInWithPassword({
email: user.email || '',
password: currentPassword
})
if (verifyError) {
return NextResponse.json(
{ error: 'Current password is incorrect' },
{ status: 400 }
)
}
Step 3: Update Password
// Source: src/app/api/auth/change-password/route.ts:89-102
// Update password and metadata
const { error: updateError } = await supabase.auth.updateUser({
password: newPassword,
data: {
password_change_required: false
}
})
if (updateError) {
return NextResponse.json(
{ error: updateError.message || 'Failed to update password' },
{ status: 500 }
)
}
Step 4: Update Staff Record
// Source: src/app/api/auth/change-password/route.ts:104-110
// Update staff table to clear password_change_required flag
await supabase
.from('staff')
.update({
password_change_required: false,
updated_at: new Date().toISOString()
})
.eq('id', user.id)
First-Time Login
Required Password Change
When you first login with a temporary password:
- Login with provided credentials
- Modal appears requiring password change
- Cannot proceed until password is changed
- Set new password meeting all requirements
First Login Detection
// Check if password change is required
const checkPasswordChangeRequired = async () => {
const { data: { user } } = await supabase.auth.getUser()
if (user?.user_metadata?.password_change_required) {
showPasswordChangeModal()
}
}
First Login Modal
// First-time login password change modal
const FirstLoginPasswordModal = ({ onComplete }) => (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
<div className="card-glass p-6 max-w-md w-full mx-4">
<h2 className="text-xl font-semibold mb-4">Change Your Password</h2>
<p className="text-secondary-text mb-6">
For security reasons, you must change your temporary password before continuing.
</p>
<PasswordChangeForm
requireCurrentPassword={false}
onSuccess={onComplete}
/>
</div>
</div>
)
Password Change Form
Form Component
// Password change form component
const PasswordChangeForm = ({
requireCurrentPassword = true,
onSuccess,
onError
}) => {
const [currentPassword, setCurrentPassword] = useState('')
const [newPassword, setNewPassword] = useState('')
const [confirmPassword, setConfirmPassword] = useState('')
const [loading, setLoading] = useState(false)
const [error, setError] = useState('')
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setError('')
// Validate passwords match
if (newPassword !== confirmPassword) {
setError('New passwords do not match')
return
}
// Validate password strength
const validation = validatePassword(newPassword)
if (!validation.valid) {
setError(validation.errors.join(', '))
return
}
setLoading(true)
try {
const response = await fetch('/api/auth/change-password', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
currentPassword: requireCurrentPassword ? currentPassword : undefined,
newPassword
})
})
const result = await response.json()
if (!response.ok) {
throw new Error(result.error || 'Failed to change password')
}
onSuccess?.()
} catch (err) {
setError(err.message)
onError?.(err)
} finally {
setLoading(false)
}
}
return (
<form onSubmit={handleSubmit} className="space-y-4">
{requireCurrentPassword && (
<div>
<label className="block text-sm font-medium mb-1">
Current Password
</label>
<input
type="password"
value={currentPassword}
onChange={(e) => setCurrentPassword(e.target.value)}
className="form-input w-full"
required
/>
</div>
)}
<div>
<label className="block text-sm font-medium mb-1">
New Password
</label>
<input
type="password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
className="form-input w-full"
required
minLength={8}
/>
<PasswordStrengthIndicator password={newPassword} />
</div>
<div>
<label className="block text-sm font-medium mb-1">
Confirm New Password
</label>
<input
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="form-input w-full"
required
/>
</div>
{error && (
<div className="p-3 bg-red-500/20 rounded text-red-200 text-sm">
{error}
</div>
)}
<button
type="submit"
disabled={loading}
className="form-button w-full"
>
{loading ? 'Changing Password...' : 'Change Password'}
</button>
</form>
)
}
Strength Indicator
// Password strength indicator component
const PasswordStrengthIndicator = ({ password }: { password: string }) => {
const strength = calculateStrength(password)
const colours = {
weak: 'bg-red-500',
fair: 'bg-yellow-500',
good: 'bg-green-500',
strong: 'bg-blue-500'
}
const widths = {
weak: 'w-1/4',
fair: 'w-2/4',
good: 'w-3/4',
strong: 'w-full'
}
if (!password) return null
return (
<div className="mt-2">
<div className="h-1 bg-white/10 rounded overflow-hidden">
<div
className={`h-full ${colours[strength]} ${widths[strength]} transition-all`}
/>
</div>
<p className="text-xs text-secondary-text mt-1">
Password strength: {strength.charAt(0).toUpperCase() + strength.slice(1)}
</p>
</div>
)
}
Password Reset
Forgot Password
If you've forgotten your password:
- Go to Login page
- Click "Forgot Password"
- Enter your email address
- Request is sent to your Company Manager
- Manager approves the reset request
- Receive new temporary password
Password Reset Request API
Endpoint: POST /api/auth/password-reset/request
// Request password reset
const requestPasswordReset = async (email: string) => {
const response = await fetch('/api/auth/password-reset/request', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email })
})
return response.json()
}
Reset Approval Workflow
| Step | Actor | Action |
|---|---|---|
| 1 | Staff | Requests password reset |
| 2 | System | Creates reset request |
| 3 | Manager | Reviews request |
| 4 | Manager | Approves or rejects |
| 5 | System | Generates temporary password |
| 6 | Staff | Receives notification |
| 7 | Staff | Logs in and changes password |
Security Best Practices
Do's and Don'ts
| ✅ Do | ❌ Don't |
|---|---|
| Use unique passwords | Reuse passwords from other sites |
| Use a password manager | Write passwords down |
| Change regularly | Share your password |
| Log out of shared devices | Use personal info in passwords |
| Report suspicious activity | Ignore security warnings |
Creating Strong Passwords
Good Password Pattern:
[Word][Number][Symbol][Word][Number]
Example: Dolphin47!Azure22
Passphrase Method:
[Word]-[Word]-[Word]-[Number]
Example: correct-horse-battery-42
Password Storage
Never Store Passwords
- Don't write passwords on paper
- Don't save in browser (shared computers)
- Don't share via email or messages
- Use a password manager instead
Error Handling
Common Errors
| Error | Cause | Solution |
|---|---|---|
| Current password incorrect | Wrong current password | Re-enter correctly |
| Password too weak | Doesn't meet requirements | Add complexity |
| Passwords don't match | Confirmation mismatch | Re-type carefully |
| Authentication required | Session expired | Login again |
Error Messages
// Error response examples
{
error: "Current password is incorrect"
// Action: Re-enter your current password
}
{
error: "Password must be at least 8 characters"
// Action: Use a longer password
}
{
error: "Failed to update password"
// Action: Try again or contact support
}
Handling Errors in UI
// Error handling in form
const handlePasswordError = (error: string) => {
switch (error) {
case 'Current password is incorrect':
// Highlight current password field
setFieldError('currentPassword', error)
break
case 'Password must be at least 8 characters':
// Show requirement hint
setFieldError('newPassword', error)
break
default:
// Show general error
setGeneralError(error)
}
}
Account Security
Session Management
After changing your password:
- Current session remains active
- Other sessions are not invalidated
- Consider logging out of other devices manually
Account Lockout
| Condition | Result |
|---|---|
| 3 failed attempts | Warning displayed |
| 5 failed attempts | Account locked 15 minutes |
| 10 failed attempts | Account locked 1 hour |
| Further attempts | Contact manager required |
Lockout Recovery
// Check if account is locked
const checkAccountStatus = async (email: string) => {
const { data: staff } = await supabase
.from('staff')
.select('locked_until, failed_login_attempts')
.eq('email', email)
.single()
if (staff?.locked_until && new Date(staff.locked_until) > new Date()) {
return {
locked: true,
until: staff.locked_until,
message: `Account locked until ${formatTime(staff.locked_until)}`
}
}
return { locked: false }
}
Two-Factor Authentication
Future Feature
Coming Soon
Two-factor authentication (2FA) will be available in a future update. This will add an extra layer of security requiring:
- Password
- One-time code from authenticator app
Preparing for 2FA
When 2FA becomes available:
- Download authenticator app (Google Authenticator, Authy)
- Enable 2FA in profile settings
- Scan QR code
- Save backup codes securely
Mobile Password Change
Mobile Form
// Mobile password change form
const MobilePasswordChange = () => (
<div className="p-4 space-y-4">
<h2 className="text-lg font-semibold">Change Password</h2>
<input
type="password"
placeholder="Current Password"
className="form-input w-full text-base py-3"
autoComplete="current-password"
/>
<input
type="password"
placeholder="New Password"
className="form-input w-full text-base py-3"
autoComplete="new-password"
/>
<input
type="password"
placeholder="Confirm New Password"
className="form-input w-full text-base py-3"
autoComplete="new-password"
/>
<button className="form-button w-full py-3 text-base">
Change Password
</button>
</div>
)
Touch Considerations
| Element | Minimum Size |
|---|---|
| Input fields | 48px height |
| Submit button | 48px height |
| Show/hide toggle | 44px × 44px |
Troubleshooting
Common Issues
| Issue | Cause | Solution |
|---|---|---|
| Can't change password | Session expired | Login again |
| Strength check failing | Missing requirement | Add required elements |
| Current password wrong | Caps lock or typo | Check input carefully |
| Form not submitting | Validation error | Check all fields |
Getting Help
If you cannot change your password:
- Try again - Check for typos
- Request reset - Use forgot password
- Contact manager - Ask for assistance
- Technical support - System Admin help
Audit Trail
Password Change Logging
// Source: src/app/api/auth/change-password/route.ts:87
// Audit logging for password changes
logMetadataChange(user.id, 'updateUser', { password_change_required: false })
What Gets Logged
| Event | Logged | Details |
|---|---|---|
| Password changed | ✅ Yes | User ID, timestamp |
| Change attempted | ✅ Yes | User ID, success/failure |
| Current password | ❌ No | Never logged |
| New password | ❌ No | Never logged |
Related Documentation
- Updating Profile - Edit your profile
- Preferences - Notification settings
- Dashboard - Staff dashboard
- Mobile App - Mobile guide
Source Files:
src/app/api/auth/change-password/route.ts- Password change APIsrc/app/api/auth/password-reset/request/route.ts- Password reset requestsrc/app/api/auth/password-reset/approve/route.ts- Reset approvalsrc/lib/utils/audit.ts- Audit logging utilities