# 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
4. **`server/src/controllers/authController.js`** - forgotPassword() & resetPassword()
5. **`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:
```typescript
const [isSuccess, setIsSuccess] = useState(false);
const [submittedEmail, setSubmittedEmail] = useState('');
```
## 🔧 Features Chi Tiết
### 1. Validation (Yup Schema)
```typescript
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
```tsx
{isLoading ? (
<>
Đang xử lý...
>
) : (
<>
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
```tsx
Cần trợ giúp?
Liên hệ: support@hotel.com hoặc 1900-xxxx
```
## 🔗 Backend Integration
### API Endpoint: Forgot Password
```
POST /api/auth/forgot-password
```
**Request Body:**
```json
{
"email": "user@example.com"
}
```
**Response (Success):**
```json
{
"status": "success",
"message": "Password reset link has been sent to your email"
}
```
**Implementation:**
```javascript
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:**
```json
{
"token": "reset_token_from_email",
"password": "NewPassword123@"
}
```
**Response (Success):**
```json
{
"status": "success",
"message": "Password has been reset successfully"
}
```
**Implementation:**
```javascript
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
```javascript
// 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
```javascript
// 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
```javascript
// 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
```sql
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()
```typescript
const { forgotPassword, isLoading, error, clearError } =
useAuthStore();
await forgotPassword({ email: data.email });
```
### Success Handling
```typescript
await forgotPassword({ email: data.email });
setIsSuccess(true); // Show success state
```
### Error Handling
```typescript
try {
await forgotPassword({ email });
} catch (error) {
// Error displayed via store.error
console.error('Forgot password error:', error);
}
```
## 🚀 Usage
### Test Frontend
```bash
URL: http://localhost:5173/forgot-password
Test Data:
Email: admin@hotel.com (from seed data)
```
### Test Backend API
```bash
curl -X POST http://localhost:3000/api/auth/forgot-password \
-H "Content-Type: application/json" \
-d '{"email":"admin@hotel.com"}'
```
Expected response:
```json
{
"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
- [x] ✅ Create ForgotPasswordPage.tsx
- [x] ✅ Implement React Hook Form
- [x] ✅ Add Yup validation
- [x] ✅ Two-state design (form + success)
- [x] ✅ Loading state
- [x] ✅ Error display
- [x] ✅ Success state with instructions
- [x] ✅ Resend email button
- [x] ✅ Back to login navigation
- [x] ✅ Help section
- [x] ✅ Integration with useAuthStore
- [x] ✅ Add route to App.tsx
- [x] ✅ Backend: forgotPassword() method
- [x] ✅ Backend: resetPassword() method
- [x] ✅ Backend: Routes added
- [x] ✅ Token generation & hashing
- [x] ✅ Token expiry (1 hour)
- [x] ✅ Security: Email enumeration prevention
- [ ] ⏳ TODO: Send actual email (nodemailer)
- [ ] ⏳ TODO: Email templates
## 📚 Related Files
- `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