Spaces:
Sleeping
Password Reset Testing Guide
Complete guide for testing the password reset functionality with audit logging.
Overview
The password reset system provides secure token-based password recovery with:
- Secure token generation (32+ characters)
- Time-limited tokens (1 hour expiry)
- Email delivery via Resend
- Comprehensive audit logging
- Protection against email enumeration attacks
- Single-use tokens
Prerequisites
Environment Variables (see
.env.example):RESEND_API_KEY=re_xxxxx RESEND_FROM_EMAIL=swiftops@atomio.tech APP_DOMAIN=swiftops.atomio.tech APP_PROTOCOL=https PASSWORD_RESET_TOKEN_EXPIRY_HOURS=1Database Migrations:
# Ensure user_invitations table exists # (Created in migration 11_user_invitations.sql)Test User:
# Register a test user first curl -X POST http://localhost:8000/api/v1/auth/register \ -H "Content-Type: application/json" \ -d '{ "email": "test@example.com", "password": "TestPass123!", "first_name": "Test", "last_name": "User" }'
Test Scenarios
1. Request Password Reset
Endpoint: POST /api/v1/auth/forgot-password
Request:
curl -X POST http://localhost:8000/api/v1/auth/forgot-password \
-H "Content-Type: application/json" \
-d '{
"email": "test@example.com"
}'
Expected Response (200 OK):
{
"message": "If an account exists with this email, you will receive a password reset link."
}
Verification:
Check database for reset token:
SELECT token, expires_at, status FROM user_invitations WHERE email = 'test@example.com' AND invitation_metadata->>'type' = 'password_reset' ORDER BY created_at DESC LIMIT 1;Check audit log:
SELECT action, description, created_at FROM audit_logs WHERE action = 'password_reset_request' ORDER BY created_at DESC LIMIT 1;Check email (if Resend configured):
- Subject: "Reset Your SwiftOps Password"
- Contains reset link with token
- Mentions 1 hour expiry
2. Reset Password with Valid Token
Endpoint: POST /api/v1/auth/reset-password
Request:
# Get token from database first
TOKEN="<token_from_database>"
curl -X POST http://localhost:8000/api/v1/auth/reset-password \
-H "Content-Type: application/json" \
-d '{
"token": "'$TOKEN'",
"new_password": "NewTestPass456!"
}'
Expected Response (200 OK):
{
"message": "Password reset successful. You can now login with your new password."
}
Verification:
Check token status updated:
SELECT status, accepted_at FROM user_invitations WHERE token = '<token>';- Status should be 'accepted'
- accepted_at should be set
Check audit log:
SELECT action, description FROM audit_logs WHERE action = 'password_reset' ORDER BY created_at DESC LIMIT 1;Login with new password:
curl -X POST http://localhost:8000/api/v1/auth/login \ -H "Content-Type: application/json" \ -d '{ "email": "test@example.com", "password": "NewTestPass456!" }'Verify old password fails:
curl -X POST http://localhost:8000/api/v1/auth/login \ -H "Content-Type: application/json" \ -d '{ "email": "test@example.com", "password": "TestPass123!" }'- Should return 401 Unauthorized
3. Invalid Token
Request:
curl -X POST http://localhost:8000/api/v1/auth/reset-password \
-H "Content-Type: application/json" \
-d '{
"token": "invalid-token-12345678901234567890",
"new_password": "NewTestPass456!"
}'
Expected Response (400 Bad Request):
{
"detail": "Invalid or expired reset token"
}
Verification:
- Check audit log for failed attempt:
SELECT action, description, additional_metadata FROM audit_logs WHERE action = 'password_reset_failed' ORDER BY created_at DESC LIMIT 1;
4. Expired Token
Setup:
-- Manually expire a token for testing
UPDATE user_invitations
SET expires_at = NOW() - INTERVAL '1 hour'
WHERE email = 'test@example.com'
AND status = 'pending'
AND invitation_metadata->>'type' = 'password_reset';
Request:
curl -X POST http://localhost:8000/api/v1/auth/reset-password \
-H "Content-Type: application/json" \
-d '{
"token": "<expired_token>",
"new_password": "NewTestPass456!"
}'
Expected Response (400 Bad Request):
{
"detail": "Reset token has expired. Please request a new one."
}
Verification:
- Token status updated to 'expired':
SELECT status FROM user_invitations WHERE token = '<expired_token>';
5. Token Reuse Prevention
Request:
# Use the same token twice
TOKEN="<already_used_token>"
# First use (should succeed)
curl -X POST http://localhost:8000/api/v1/auth/reset-password \
-H "Content-Type: application/json" \
-d '{
"token": "'$TOKEN'",
"new_password": "NewTestPass456!"
}'
# Second use (should fail)
curl -X POST http://localhost:8000/api/v1/auth/reset-password \
-H "Content-Type: application/json" \
-d '{
"token": "'$TOKEN'",
"new_password": "AnotherPass789!"
}'
Expected:
- First request: 200 OK
- Second request: 400 Bad Request
6. Non-Existent Email (No Enumeration)
Request:
curl -X POST http://localhost:8000/api/v1/auth/forgot-password \
-H "Content-Type: application/json" \
-d '{
"email": "nonexistent@example.com"
}'
Expected Response (200 OK):
{
"message": "If an account exists with this email, you will receive a password reset link."
}
Verification:
- Same response as valid email (prevents enumeration)
- No token created in database
- Audit log still created
7. Password Validation
Weak Passwords to Test:
# Too short
curl -X POST http://localhost:8000/api/v1/auth/reset-password \
-H "Content-Type: application/json" \
-d '{"token": "test", "new_password": "short"}'
# No uppercase
curl -X POST http://localhost:8000/api/v1/auth/reset-password \
-H "Content-Type: application/json" \
-d '{"token": "test", "new_password": "nouppercase123"}'
# No digits
curl -X POST http://localhost:8000/api/v1/auth/reset-password \
-H "Content-Type: application/json" \
-d '{"token": "test", "new_password": "NoDigitsHere"}'
Expected: All should return 422 Validation Error
Automated Tests
Python Tests
Run pytest tests:
# All password reset tests
pytest tests/integration/test_password_reset.py -v
# Specific test
pytest tests/integration/test_password_reset.py::TestPasswordReset::test_reset_password_with_valid_token -v
# All auth audit log tests
pytest tests/integration/test_auth_audit_logs.py -v
JavaScript Tests
Run Node.js tests:
cd tests/integration
npm install axios # If not already installed
node test_password_reset.js
Audit Log Verification
Check all audit logs for password reset flow:
-- All password reset related logs
SELECT
action,
description,
user_email,
ip_address,
created_at
FROM audit_logs
WHERE action IN (
'password_reset_request',
'password_reset',
'password_reset_failed'
)
ORDER BY created_at DESC
LIMIT 20;
-- Detailed view with metadata
SELECT
action,
description,
additional_metadata,
created_at
FROM audit_logs
WHERE entity_type = 'auth'
AND action LIKE '%password%'
ORDER BY created_at DESC;
Email Template Testing
If Resend is configured, verify email appearance:
- Subject: "Reset Your SwiftOps Password"
- Content:
- Personalized greeting with user's name
- Clear call-to-action button
- Plain text link as fallback
- Security notice about 1-hour expiry
- Warning if user didn't request reset
- Styling:
- Responsive design
- Professional appearance
- SwiftOps branding
Security Checklist
- Tokens are cryptographically secure (32+ characters)
- Tokens expire after 1 hour
- Tokens are single-use only
- No email enumeration (same response for valid/invalid emails)
- Password strength requirements enforced
- All actions logged in audit trail
- IP addresses captured in audit logs
- User agents captured in audit logs
- Email delivery failures handled gracefully
- Database errors don't expose sensitive info
Troubleshooting
Email Not Received
Check Resend API key:
echo $RESEND_API_KEYCheck application logs:
grep "Password reset" logs/app.log grep "Email send error" logs/app.logVerify Resend domain configuration
Token Not Working
Check token exists and is valid:
SELECT * FROM user_invitations WHERE token = '<token>';Check expiry time:
SELECT token, expires_at, expires_at > NOW() as is_valid, status FROM user_invitations WHERE token = '<token>';
Audit Logs Not Created
- Check database connection
- Verify audit_logs table exists
- Check application logs for errors
- Ensure AuditService is imported correctly
Performance Considerations
- Token generation: < 10ms
- Database query: < 50ms
- Email sending: < 2s (async)
- Total request time: < 100ms (excluding email)
Next Steps
After testing password reset:
- Test all auth endpoints with audit logging
- Verify audit log retention policies
- Set up monitoring for failed reset attempts
- Configure email rate limiting
- Review security policies with team