Password Reset Flow Implementation Guide
Password Reset Flow Implementation Guide
Section titled “Password Reset Flow Implementation Guide”This document provides comprehensive documentation of the password reset flow in Bartendie, including the ForgotPasswordPage and ResetPasswordPage components, security features, and backend integration requirements.
Overview
Section titled “Overview”The password reset flow consists of two main components:
- ForgotPasswordPage - Initiates password reset by requesting email
- ResetPasswordPage - Completes password reset with token validation
The implementation includes security features like email enumeration protection, token expiration, and comprehensive validation.
Architecture Overview
Section titled “Architecture Overview”User Flow:1. User clicks "Forgot Password" → ForgotPasswordPage2. User enters email → REQUEST_PASSWORD_RESET_MUTATION3. User receives email with reset link4. User clicks link → ResetPasswordPage (with token)5. User enters new password → RESET_PASSWORD_MUTATION6. Password reset complete → Redirect to loginForgotPasswordPage Component
Section titled “ForgotPasswordPage Component”File Structure
Section titled “File Structure”Location: frontend/src/components/auth/ForgotPasswordPage.tsx
GraphQL Integration
Section titled “GraphQL Integration”const REQUEST_PASSWORD_RESET_MUTATION = gql` mutation RequestPasswordReset($email: String!) { requestPasswordReset(email: $email) { success message } }`;Features:
- Simple Input: Only requires email address
- Success Response: Boolean success flag with optional message
- Error Handling: Graceful error management
TypeScript Interfaces
Section titled “TypeScript Interfaces”interface ForgotPasswordFormData { email: string;}
interface ForgotPasswordError { message: string; field?: string;}Design Principles:
- Minimal Data: Only email required
- Field-Specific Errors: Targeted error messaging
- Type Safety: Full TypeScript support
Security Features
Section titled “Security Features”Email Enumeration Protection
Section titled “Email Enumeration Protection”try { const { data } = await requestPasswordResetMutation({ variables: { email: formData.email }, });
if (data.requestPasswordReset.success) { setIsSuccess(true); } else { setError({ message: data.requestPasswordReset.message || 'Failed to send reset email.' }); }} catch (err: any) { // For security reasons, we don't want to reveal if an email exists or not // So we'll show success even if the email doesn't exist setIsSuccess(true);}Security Benefits:
- No Email Enumeration: Always shows success to prevent email discovery
- Consistent Response: Same response time regardless of email existence
- User-Friendly: Doesn’t confuse legitimate users
Success State Implementation
Section titled “Success State Implementation”// Success screen with security messaging<div className="text-center space-y-4"> <div className="mx-auto w-12 h-12 bg-green-100 rounded-full flex items-center justify-center"> <CheckCircle className="h-6 w-6 text-green-600" /> </div>
<div className="space-y-2"> <h3 className="text-lg font-semibold text-gray-900">Check your email</h3> <p className="text-sm text-gray-600"> If an account with <strong>{formData.email}</strong> exists, we've sent you a password reset link. </p>
<p className="text-sm text-gray-600"> The reset link will expire in <strong>1 hour</strong> for security reasons. </p> </div></div>Validation Logic
Section titled “Validation Logic”const validateEmail = (email: string): boolean => { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email);};
const validateForm = (): boolean => { if (!formData.email) { setError({ message: 'Email is required', field: 'email' }); return false; }
if (!validateEmail(formData.email)) { setError({ message: 'Please enter a valid email address', field: 'email' }); return false; }
return true;};User Experience Features
Section titled “User Experience Features”Try Again Functionality
Section titled “Try Again Functionality”const handleTryAgain = () => { setIsSuccess(false); setFormData({ email: '' }); setError(null);};Navigation Options
Section titled “Navigation Options”// Multiple ways to continue<div className="space-y-3"> <Button onClick={handleTryAgain} variant="outline" className="w-full"> Send to a different email </Button>
<Link to="/login" className="block"> <Button variant="ghost" className="w-full"> <ArrowLeft className="h-4 w-4 mr-2" /> Back to sign in </Button> </Link></div>ResetPasswordPage Component
Section titled “ResetPasswordPage Component”File Structure
Section titled “File Structure”Location: frontend/src/components/auth/ResetPasswordPage.tsx
GraphQL Integration
Section titled “GraphQL Integration”const RESET_PASSWORD_MUTATION = gql` mutation ResetPassword($token: String!, $password: String!, $confirmPassword: String!) { resetPassword(token: $token, password: $password, confirmPassword: $confirmPassword) { success message } }`;Features:
- Token Validation: Server-side token verification
- Password Confirmation: Double-entry password validation
- Success Response: Boolean success with optional message
TypeScript Interfaces
Section titled “TypeScript Interfaces”interface ResetPasswordFormData { password: string; confirmPassword: string;}
interface ResetPasswordError { message: string; field?: string;}
interface PasswordRequirement { label: string; test: (password: string) => boolean;}Token Handling
Section titled “Token Handling”URL Parameter Extraction
Section titled “URL Parameter Extraction”const [searchParams] = useSearchParams();const navigate = useNavigate();const token = searchParams.get('token');
// Redirect if no token is provideduseEffect(() => { if (!token) { navigate('/forgot-password', { replace: true }); }}, [token, navigate]);Security Features:
- Automatic Redirect: Redirects to forgot password if no token
- Token Validation: Server-side token verification
- Replace Navigation: Prevents back button issues
Token Error Handling
Section titled “Token Error Handling”try { const { data } = await resetPasswordMutation({ variables: { token, password: formData.password, confirmPassword: formData.confirmPassword, }, });
if (data.resetPassword.success) { setIsSuccess(true); } else { setError({ message: data.resetPassword.message || 'Failed to reset password.' }); }} catch (err: any) { if (err.message.includes('token')) { setError({ message: 'This reset link has expired or is invalid. Please request a new one.' }); } else { setError({ message: err.message || 'Failed to reset password. Please try again.' }); }}Password Strength Implementation
Section titled “Password Strength Implementation”Requirements Configuration
Section titled “Requirements Configuration”const passwordRequirements: PasswordRequirement[] = [ { label: 'At least 8 characters', test: (pwd) => pwd.length >= 8 }, { label: 'Contains uppercase letter', test: (pwd) => /[A-Z]/.test(pwd) }, { label: 'Contains lowercase letter', test: (pwd) => /[a-z]/.test(pwd) }, { label: 'Contains number', test: (pwd) => /\d/.test(pwd) }, { label: 'Contains special character', test: (pwd) => /[!@#$%^&*(),.?":{}|<>]/.test(pwd) },];Strength Calculation
Section titled “Strength Calculation”const getPasswordStrength = (): { score: number; label: string; color: string } => { const metRequirements = passwordRequirements.filter(req => req.test(formData.password)).length;
if (metRequirements <= 2) return { score: metRequirements, label: 'Weak', color: 'text-red-600' }; if (metRequirements <= 3) return { score: metRequirements, label: 'Fair', color: 'text-yellow-600' }; if (metRequirements <= 4) return { score: metRequirements, label: 'Good', color: 'text-blue-600' }; return { score: metRequirements, label: 'Strong', color: 'text-green-600' };};Visual Indicator
Section titled “Visual Indicator”{/* Password Strength Indicator */}{formData.password && ( <div className="space-y-2"> <div className="flex items-center justify-between"> <span className="text-xs text-gray-500">Password strength:</span> <span className={`text-xs font-medium ${passwordStrength.color}`}> {passwordStrength.label} </span> </div> <div className="w-full bg-gray-200 rounded-full h-1"> <div className={`h-1 rounded-full transition-all duration-300 ${ passwordStrength.score <= 2 ? 'bg-red-500' : passwordStrength.score <= 3 ? 'bg-yellow-500' : passwordStrength.score <= 4 ? 'bg-blue-500' : 'bg-green-500' }`} style={{ width: `${(passwordStrength.score / 5) * 100}%` }} /> </div> </div>)}Real-Time Validation
Section titled “Real-Time Validation”Password Requirements Display
Section titled “Password Requirements Display”{/* Password Requirements */}{showPasswordRequirements && ( <div className="space-y-1 p-3 bg-gray-50 rounded-md"> <p className="text-xs font-medium text-gray-700 mb-2">Password must contain:</p> {passwordRequirements.map((req, index) => { const isMet = req.test(formData.password); return ( <div key={index} className="flex items-center gap-2 text-xs"> {isMet ? ( <Check className="h-3 w-3 text-green-600" /> ) : ( <X className="h-3 w-3 text-gray-400" /> )} <span className={isMet ? 'text-green-600' : 'text-gray-500'}> {req.label} </span> </div> ); })} </div>)}Password Match Validation
Section titled “Password Match Validation”{/* Password Match Indicator */}{formData.confirmPassword && ( <div className="flex items-center gap-2 text-xs"> {formData.password === formData.confirmPassword ? ( <> <Check className="h-3 w-3 text-green-600" /> <span className="text-green-600">Passwords match</span> </> ) : ( <> <X className="h-3 w-3 text-red-600" /> <span className="text-red-600">Passwords do not match</span> </> )} </div>)}Success State
Section titled “Success State”// Success screen after password reset{isSuccess && ( <div className="text-center space-y-4"> <div className="mx-auto w-12 h-12 bg-green-100 rounded-full flex items-center justify-center"> <CheckCircle className="h-6 w-6 text-green-600" /> </div>
<div className="space-y-2"> <h3 className="text-lg font-semibold text-gray-900">Password reset successful!</h3> <p className="text-sm text-gray-600"> Your password has been successfully updated. You can now sign in with your new password. </p> </div>
<Link to="/login" className="block"> <Button className="w-full"> Continue to sign in </Button> </Link> </div>)}Backend Integration Requirements
Section titled “Backend Integration Requirements”Expected GraphQL Schema
Section titled “Expected GraphQL Schema”type Mutation { requestPasswordReset(email: String!): PasswordResetResponse! resetPassword(token: String!, password: String!, confirmPassword: String!): PasswordResetResponse!}
type PasswordResetResponse { success: Boolean! message: String}Security Requirements
Section titled “Security Requirements”Token Generation
Section titled “Token Generation”- Cryptographically Secure: Use secure random token generation
- Expiration: 1-hour expiration for security
- Single Use: Tokens should be invalidated after use
- Database Storage: Store hashed tokens, not plain text
Email Handling
Section titled “Email Handling”- Rate Limiting: Prevent spam by limiting requests per IP/email
- Template Security: Use secure email templates
- Link Format:
https://domain.com/reset-password?token=<secure_token>
Password Validation
Section titled “Password Validation”- Server-Side Validation: Validate all password requirements on backend
- Hash Storage: Use bcrypt or similar for password hashing
- Audit Logging: Log password reset attempts for security
Error Handling
Section titled “Error Handling”Expected Error Responses
Section titled “Expected Error Responses”// Invalid token{ "errors": [ { "message": "Invalid or expired reset token", "extensions": { "code": "INVALID_TOKEN" } } ]}
// Password validation failure{ "errors": [ { "message": "Password does not meet security requirements", "extensions": { "code": "WEAK_PASSWORD" } } ]}Responsive Design
Section titled “Responsive Design”Mobile Optimization
Section titled “Mobile Optimization”<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-gray-50 to-gray-100 py-12 px-4 sm:px-6 lg:px-8"> <div className="w-full max-w-md space-y-8">Accessibility Features
Section titled “Accessibility Features”- Screen Reader Support: Proper ARIA labels and descriptions
- Keyboard Navigation: Full keyboard accessibility
- Focus Management: Logical tab order
- Error Announcements: Live regions for dynamic content
Testing Strategy
Section titled “Testing Strategy”Unit Tests
Section titled “Unit Tests”describe('ForgotPasswordPage', () => { test('validates email format', () => { // Test email validation });
test('shows success state after submission', () => { // Test success flow });
test('handles API errors gracefully', () => { // Test error handling });});
describe('ResetPasswordPage', () => { test('redirects when no token provided', () => { // Test token requirement });
test('validates password requirements', () => { // Test password validation });
test('handles expired tokens', () => { // Test token expiration });});Integration Tests
Section titled “Integration Tests”test('complete password reset flow', async () => { // 1. Request password reset // 2. Simulate email click // 3. Reset password // 4. Verify success});Security Considerations
Section titled “Security Considerations”Frontend Security
Section titled “Frontend Security”- No Sensitive Data Storage: Never store tokens in localStorage
- HTTPS Only: Ensure all requests use HTTPS
- Input Sanitization: Validate all user inputs
- Error Message Security: Don’t reveal system information
Backend Security
Section titled “Backend Security”- Token Expiration: Short-lived tokens (1 hour)
- Rate Limiting: Prevent abuse
- Audit Logging: Track all reset attempts
- Email Verification: Verify email ownership
This comprehensive password reset flow provides a secure, user-friendly experience while protecting against common security vulnerabilities.