Files
Hotel-Booking/docs/FORGOT_PASSWORD_COMPLETE.md
Iliyan Angelov 824eec6190 Hotel Booking
2025-11-16 14:19:13 +02:00

12 KiB
Raw Blame History

Chức năng 6: Quên Mật Khẩu (Forgot Password) - Hoàn Thành

📦 Files Đã Tạo/Cập Nhật

Frontend

  1. client/src/pages/auth/ForgotPasswordPage.tsx - Component form quên mật khẩu
  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 cho forgot/reset password

Tính Năng Chính

1. Form State (Initial)

┌─────────────────────────────────────┐
│   🏨 Hotel Icon (Blue)              │
│   Quên mật khẩu?                    │
│   Nhập email để nhận link...        │
├─────────────────────────────────────┤
│  ┌───────────────────────────────┐  │
│  │ Email                         │  │
│  │ [📧 email@example.com     ]   │  │
│  ├───────────────────────────────┤  │
│  │  [📤 Gửi link đặt lại MK]     │  │
│  ├───────────────────────────────┤  │
│  │ ← Quay lại đăng nhập          │  │
│  └───────────────────────────────┘  │
│  Chưa có tài khoản? Đăng ký ngay    │
└─────────────────────────────────────┘

2. Success State (After Submit)

┌─────────────────────────────────────┐
│           ✅ Success Icon            │
│                                     │
│      Email đã được gửi!             │
│   Chúng tôi đã gửi link đến         │
│      user@example.com               │
├─────────────────────────────────────┤
│   Lưu ý:                           │
│  • Link có hiệu lực trong 1 giờ     │
│  • Kiểm tra cả thư mục Spam/Junk    │
│  • Nếu không nhận được, thử lại     │
├─────────────────────────────────────┤
│  [📧 Gửi lại email]                 │
│  [← Quay lại đăng nhập]             │
└─────────────────────────────────────┘

3. Two-State Design Pattern

Form State - Nhập email Success State - Hiển thị xác nhận & hướng dẫn

State management:

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

🔧 Features Chi Tiết

1. Validation (Yup Schema)

email:
  - Required: "Email là bắt buộc"
  - Valid format: "Email không hợp lệ"
  - Trim whitespace

2. Form Field

  • Email input với Mail icon
  • Auto-focus khi load page
  • Validation real-time
  • Error message inline

3. Submit Button States

{isLoading ? (
  <>
    <Loader2 className="animate-spin" />
    Đang xử ...
  </>
) : (
  <>
    <Send />
    Gửi link đặt lại mật khẩu
  </>
)}

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

  • "Gửi lại email" - Reset to form state
  • "Quay lại đăng nhập" - Navigate to /login

5. Help Section

<div className="bg-white rounded-lg shadow-sm border">
  <h3>Cần trợ giúp?</h3>
  <p>
    Liên hệ: support@hotel.com hoặc 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 "Gửi link đặt lại mật khẩu"
        ↓
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 (Chức năng 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: "Email không hợp lệ"
  - Form not submitted

Test Case 3: Empty email

Input: email = ""
Expected:
  - Validation error: "Email là bắt buộc"
  - Form not submitted

Test Case 4: Loading state

Action: Submit form
Expected:
  - Button disabled
  - Spinner shows
  - Text: "Đang xử lý..."

Test Case 5: Resend email

Action: Click "Gửi lại email" in success state
Expected:
  - Return to form state
  - Email field cleared
  - Error cleared

Test Case 6: Back to login

Action: Click "Quay lại đăng nhập"
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: Chức năng 6 hoàn thành Next: Chức năng 7 - Reset Password (form to change password with token) Test URL: http://localhost:5173/forgot-password API: POST /api/auth/forgot-password