Files
Hotel-Booking/docs/FORGOT_PASSWORD_COMPLETE.md
Iliyan Angelov 93d4c1df80 update
2025-11-16 15:12:43 +02:00

12 KiB
Raw Blame History

Function 6: Forgot Password - Completed

📦 Files Created/Updated

Frontend

  1. client/src/pages/auth/ForgotPasswordPage.tsx - Forgot password form component
  2. client/src/pages/auth/index.ts - Export ForgotPasswordPage
  3. client/src/App.tsx - Route /forgot-password

Backend

  1. server/src/controllers/authController.js - forgotPassword() & resetPassword()
  2. server/src/routes/authRoutes.js - Routes for forgot/reset password

Main Features

1. Form State (Initial)

┌─────────────────────────────────────┐
│   🏨 Hotel Icon (Blue)              │
│   Forgot password?                  │
│   Enter email to receive link...    │
├─────────────────────────────────────┤
│  ┌───────────────────────────────┐  │
│  │ Email                         │  │
│  │ [📧 email@example.com     ]   │  │
│  ├───────────────────────────────┤  │
│  │  [📤 Send reset link]          │  │
│  ├───────────────────────────────┤  │
│  │ ← Back to login               │  │
│  └───────────────────────────────┘  │
│  Don't have an account? Sign up now │
└─────────────────────────────────────┘

2. Success State (After Submit)

┌─────────────────────────────────────┐
│           ✅ Success Icon            │
│                                     │
│      Email has been sent!           │
│   We have sent a link to            │
│      user@example.com               │
├─────────────────────────────────────┤
│   Note:                            │
│  • Link is valid for 1 hour         │
│  • Check Spam/Junk folder           │
│  • If not received, try again       │
├─────────────────────────────────────┤
│  [📧 Resend email]                  │
│  [← Back to login]                  │
└─────────────────────────────────────┘

3. Two-State Design Pattern

Form State - Enter email Success State - Display confirmation & instructions

State management:

const [isSuccess, setIsSuccess] = useState(false);
const [submittedEmail, setSubmittedEmail] = useState('');

🔧 Detailed Features

1. Validation (Yup Schema)

email:
  - Required: "Email is required"
  - Valid format: "Invalid email format"
  - Trim whitespace

2. Form Field

  • Email input with Mail icon
  • Auto-focus when page loads
  • Validation real-time
  • Error message inline

3. Submit Button States

{isLoading ? (
  <>
    <Loader2 className="animate-spin" />
    Processing...
  </>
) : (
  <>
    <Send />
    Send reset link
  </>
)}

4. Success Features

Visual Confirmation

  • Green checkmark icon
  • Success message
  • Display submitted email

User Instructions

  • Link expires in 1 hour
  • Check spam folder
  • Can resend email

Action Buttons

  • "Resend email" - Reset to form state
  • "Back to login" - Navigate to /login

5. Help Section

<div className="bg-white rounded-lg shadow-sm border">
  <h3>Need help?</h3>
  <p>
    Contact: support@hotel.com or 1900-xxxx
  </p>
</div>

🔗 Backend Integration

API Endpoint: Forgot Password

POST /api/auth/forgot-password

Request Body:

{
  "email": "user@example.com"
}

Response (Success):

{
  "status": "success",
  "message": "Password reset link has been sent to your email"
}

Implementation:

const forgotPassword = async (req, res, next) => {
  // 1. Find user by email
  // 2. Generate crypto reset token (32 bytes)
  // 3. Hash token with SHA256
  // 4. Save to password_reset_tokens table
  // 5. Expires in 1 hour
  // 6. TODO: Send email with reset link
  // 7. Return success (prevent email enumeration)
};

API Endpoint: Reset Password

POST /api/auth/reset-password

Request Body:

{
  "token": "reset_token_from_email",
  "password": "NewPassword123@"
}

Response (Success):

{
  "status": "success",
  "message": "Password has been reset successfully"
}

Implementation:

const resetPassword = async (req, res, next) => {
  // 1. Hash incoming token
  // 2. Find valid token in DB (not expired)
  // 3. Find user by user_id
  // 4. Hash new password with bcrypt
  // 5. Update user password
  // 6. Delete used token
  // 7. TODO: Send confirmation email
  // 8. Return success
};

🔐 Security Features

1. Token Generation

// Generate random 32-byte token
const resetToken = crypto.randomBytes(32).toString('hex');

// Hash with SHA256 before storing
const hashedToken = crypto
  .createHash('sha256')
  .update(resetToken)
  .digest('hex');

2. Token Storage

  • Stored in password_reset_tokens table
  • Hashed (SHA256)
  • Expires in 1 hour
  • One token per user (old tokens deleted)

3. Email Enumeration Prevention

// Always return success, even if email not found
if (!user) {
  return res.status(200).json({
    status: 'success',
    message: 'If email exists, reset link has been sent'
  });
}

4. Token Validation

// Check token exists and not expired
const resetToken = await PasswordResetToken.findOne({
  where: {
    token: hashedToken,
    expires_at: { [Op.gt]: new Date() }
  }
});

📊 Database Schema

password_reset_tokens Table

CREATE TABLE password_reset_tokens (
  id INT PRIMARY KEY AUTO_INCREMENT,
  user_id INT NOT NULL,
  token VARCHAR(255) NOT NULL,
  expires_at DATETIME NOT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (user_id) REFERENCES users(id)
);

🎨 Design & Styling

Color Scheme:

  • Primary: blue-600, blue-700
  • Background: gradient from-blue-50 to-indigo-100
  • Success: green-100, green-600
  • Info: blue-50, blue-200
  • Text: gray-600, gray-700, gray-900

Icons:

  • 🏨 Hotel - Main logo
  • 📧 Mail - Email input
  • 📤 Send - Submit button
  • Loader2 - Loading spinner
  • CheckCircle - Success state
  • ← ArrowLeft - Back navigation

🔄 User Flow

1. User visits /forgot-password
        ↓
2. Enter email address
        ↓
3. Click "Send reset link"
        ↓
4. Frontend validation (Yup)
        ↓
5. Call useAuthStore.forgotPassword()
        ↓
6. API POST /api/auth/forgot-password
        ↓
7. Backend:
   - Generate token
   - Save to DB
   - TODO: Send email
        ↓
8. Frontend shows success state
        ↓
9. User receives email (TODO)
        ↓
10. Click link → /reset-password/:token
        ↓
11. Enter new password (Function 7)

🧪 Test Scenarios

Test Case 1: Valid email

Input: email = "user@example.com"
Expected:
  - API called successfully
  - Success state shown
  - Email displayed correctly
  - Instructions visible

Test Case 2: Invalid email format

Input: email = "notanemail"
Expected:
  - Validation error: "Invalid email format"
  - Form not submitted

Test Case 3: Empty email

Input: email = ""
Expected:
  - Validation error: "Email is required"
  - Form not submitted

Test Case 4: Loading state

Action: Submit form
Expected:
  - Button disabled
  - Spinner shows
  - Text: "Processing..."

Test Case 5: Resend email

Action: Click "Resend email" in success state
Expected:
  - Return to form state
  - Email field cleared
  - Error cleared

Test Case 6: Back to login

Action: Click "Back to login"
Expected:
  - Navigate to /login

Test Case 7: Email not found (Backend)

Input: email = "nonexistent@example.com"
Expected:
  - Still return success (security)
  - No error shown to user

Test Case 8: Token expiry (Backend)

Scenario: Token created 2 hours ago
Expected:
  - Reset fails
  - Error: "Invalid or expired reset token"

📝 Code Quality

TypeScript: Full type safety React Hook Form: Optimized performance Yup Validation: Schema-based validation State Management: Local state for success toggle Error Handling: Try-catch blocks Security: Token hashing, email enumeration prevention UX: Clear instructions, help section Accessibility: Labels, autocomplete, focus management Responsive: Mobile-friendly 80 chars/line: Code formatting

🔗 Integration Points

useAuthStore.forgotPassword()

const { forgotPassword, isLoading, error, clearError } = 
  useAuthStore();

await forgotPassword({ email: data.email });

Success Handling

await forgotPassword({ email: data.email });
setIsSuccess(true); // Show success state

Error Handling

try {
  await forgotPassword({ email });
} catch (error) {
  // Error displayed via store.error
  console.error('Forgot password error:', error);
}

🚀 Usage

Test Frontend

URL: http://localhost:5173/forgot-password

Test Data:
Email: admin@hotel.com (from seed data)

Test Backend API

curl -X POST http://localhost:3000/api/auth/forgot-password \
  -H "Content-Type: application/json" \
  -d '{"email":"admin@hotel.com"}'

Expected response:

{
  "status": "success",
  "message": "Password reset link has been sent to your email"
}

Ensure SMTP is configured in server/.env and that the server is able to send emails. The application must never log raw reset tokens or reset URLs in production.

⚠️ TODO: Email Service

The project must send real emails via SMTP in production and must never expose raw reset tokens in logs. To enable email sending:

  1. Install nodemailer in the server package and configure SMTP credentials in server/.env (MAIL_HOST, MAIL_PORT, MAIL_USER, MAIL_PASS, MAIL_FROM). Do not commit these credentials to source control.

  2. Implement a mail helper (e.g. server/src/utils/mailer.js) that uses the SMTP settings. Ensure it throws or fails when SMTP credentials are missing so that emails are not silently dropped.

  3. Use the mail helper to send the reset email with a link built as ${process.env.CLIENT_URL}/reset-password/${resetToken}.

  4. Important: never log the raw resetToken or the reset URL. If email sending fails, log a generic error and surface a safe message to the user.

Checklist

  • Create ForgotPasswordPage.tsx
  • Implement React Hook Form
  • Add Yup validation
  • Two-state design (form + success)
  • Loading state
  • Error display
  • Success state with instructions
  • Resend email button
  • Back to login navigation
  • Help section
  • Integration with useAuthStore
  • Add route to App.tsx
  • Backend: forgotPassword() method
  • Backend: resetPassword() method
  • Backend: Routes added
  • Token generation & hashing
  • Token expiry (1 hour)
  • Security: Email enumeration prevention
  • TODO: Send actual email (nodemailer)
  • TODO: Email templates
  • client/src/pages/auth/LoginPage.tsx - Login form
  • client/src/pages/auth/RegisterPage.tsx - Register form
  • client/src/utils/validationSchemas.ts - Validation schemas
  • client/src/store/useAuthStore.ts - Auth state
  • server/src/controllers/authController.js - Auth logic
  • server/src/routes/authRoutes.js - Auth routes
  • server/src/databases/models/PasswordResetToken.js - Token model

Status: Function 6 completed Next: Function 7 - Reset Password (form to change password with token) Test URL: http://localhost:5173/forgot-password API: POST /api/auth/forgot-password