This commit is contained in:
Ken
2026-01-19 09:33:35 +07:00
parent 374dc12b2d
commit 70838a4bc1
103 changed files with 16929 additions and 2 deletions

344
LOGIN_GUIDE.md Normal file
View File

@@ -0,0 +1,344 @@
# 🔐 Hướng Dẫn Hệ Thống Đăng Nhập
## 📋 Mô Tả Quy Trình Đăng Nhập
### 1⃣ Kiến Trúc Hệ Thống
```
┌─────────────┐ ┌──────────────┐ ┌──────────────┐
│ Browser │────▶│ API Server │────▶│ Database │
│ (login.html)│◀────│ (Express) │◀────│ (MySQL) │
└─────────────┘ └──────────────┘ └──────────────┘
┌──────────────┐
│ Redis │
│ (Cache) │
└──────────────┘
```
### 2⃣ Quy Trình Đăng Nhập Chi Tiết
#### **Bước 1: User Nhập Thông Tin**
- Frontend (login.html) hiển thị form đăng nhập
- User nhập `username` hoặc `email``password`
- Click button "Đăng nhập"
#### **Bước 2: Gửi Request Đến Server**
```javascript
POST /api/users/login
Content-Type: application/json
{
"username": "admin", // hoặc email
"password": "admin123"
}
```
#### **Bước 3: Server Xác Thực**
**3.1. Tìm User trong Database**
```javascript
// Tìm user theo username HOẶC email
UsersAuth.findOne({
where: {
OR: [
{ username: username },
{ email: username }
]
}
})
```
**3.2. Kiểm Tra Các Điều Kiện**
- ❌ User không tồn tại → Trả về lỗi 401
- ❌ Tài khoản bị khóa (`is_locked = true`) → Trả về lỗi 403
- ❌ Tài khoản không active (`is_active = false`) → Trả về lỗi 403
**3.3. Verify Password**
```javascript
// Password được hash theo công thức:
// hash = bcrypt(password + salt)
const passwordMatch = await bcrypt.compare(
password + user.salt,
user.password_hash
);
```
**3.4. Xử Lý Kết Quả**
**❌ Nếu Password Sai:**
- Tăng `failed_login_attempts` lên 1
- Nếu thất bại ≥ 5 lần:
- Khóa tài khoản (`is_locked = true`)
- Set `locked_until` = hiện tại + 30 phút
- Trả về lỗi 401
**✅ Nếu Password Đúng:**
- Reset `failed_login_attempts = 0`
- Tăng `login_count` lên 1
- Cập nhật `last_login` = thời gian hiện tại
- Cập nhật `last_login_ip` = IP của client
- Tạo `session_id` mới (UUID)
#### **Bước 4: Tạo JWT Token**
```javascript
const token = jwt.sign(
{
userId: user.id,
username: user.username,
email: user.email,
sessionId: sessionId
},
JWT_SECRET,
{ expiresIn: '24h' }
);
```
#### **Bước 5: Trả Response Về Client**
```json
{
"success": true,
"message": "Đăng nhập thành công",
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "uuid-here",
"username": "admin",
"email": "admin@senaai.tech",
"profile": { ... },
"last_login": "2026-01-19T...",
"login_count": 42
}
}
}
```
#### **Bước 6: Client Lưu Token**
```javascript
// Lưu token vào localStorage
localStorage.setItem('token', data.data.token);
```
### 3⃣ Sử Dụng Token Để Xác Thực
**Các Request Tiếp Theo:**
```javascript
fetch('/api/users/profile', {
headers: {
'Authorization': 'Bearer ' + token
}
})
```
**Server Verify Token:**
```javascript
POST /api/users/verify-token
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```
### 4⃣ Đăng Xuất
```javascript
POST /api/users/logout
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
// Server sẽ:
// 1. Xóa current_session_id trong database
// 2. Token sẽ không còn valid nữa
```
## 🔒 Bảo Mật
### Password Security
- **Salt**: Mỗi user có salt riêng (16 bytes random)
- **Hash Algorithm**: bcrypt với cost factor = 10
- **Formula**: `hash = bcrypt(password + salt, 10)`
### Token Security
- **Algorithm**: JWT với HS256
- **Expiration**: 24 giờ
- **Secret Key**: Lưu trong environment variable
- **Session Tracking**: Mỗi lần login tạo session_id mới
### Brute Force Protection
- Khóa tài khoản sau 5 lần đăng nhập sai
- Thời gian khóa: 30 phút
- Tự động mở khóa sau khi hết thời gian
## 📊 Database Schema
### Table: `users_auth`
```sql
- id (UUID, PK)
- username (VARCHAR, UNIQUE)
- email (VARCHAR, UNIQUE)
- password_hash (VARCHAR)
- salt (VARCHAR)
- current_session_id (VARCHAR)
- last_login (DATETIME)
- last_login_ip (VARCHAR)
- login_count (INT)
- failed_login_attempts (INT)
- is_active (BOOLEAN)
- is_locked (BOOLEAN)
- locked_until (DATETIME)
```
## 🚀 API Endpoints
### Authentication
| Method | Endpoint | Mô Tả |
|--------|----------|-------|
| POST | `/api/users/login` | Đăng nhập |
| POST | `/api/users/register` | Đăng ký tài khoản mới |
| POST | `/api/users/verify-token` | Xác thực token |
| POST | `/api/users/logout` | Đăng xuất |
### User Management
| Method | Endpoint | Mô Tả |
|--------|----------|-------|
| GET | `/api/users` | Lấy danh sách users |
| GET | `/api/users/:id` | Lấy thông tin user theo ID |
| POST | `/api/users` | Tạo user mới |
| PUT | `/api/users/:id` | Cập nhật user |
| DELETE | `/api/users/:id` | Xóa user (soft delete) |
## 🧪 Testing
### 1. Tạo Test Users
```bash
node scripts/create-test-user.js
```
### 2. Mở Login Page
```
http://localhost:4000/login.html
```
### 3. Test Login
**Test Accounts:**
- **Admin**: `admin` / `admin123`
- **Teacher**: `teacher1` / `teacher123`
- **Student**: `student1` / `student123`
### 4. Test Với cURL
**Login:**
```bash
curl -X POST http://localhost:4000/api/users/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin123"}'
```
**Verify Token:**
```bash
curl -X POST http://localhost:4000/api/users/verify-token \
-H "Authorization: Bearer YOUR_TOKEN_HERE"
```
**Logout:**
```bash
curl -X POST http://localhost:4000/api/users/logout \
-H "Authorization: Bearer YOUR_TOKEN_HERE"
```
## 🎯 Luồng Xử Lý (Flow Diagram)
```
User Input
[Frontend] login.html
│ POST /api/users/login
│ { username, password }
[Backend] userController.login()
├──▶ Tìm user trong DB
│ ├─ Không tìm thấy → 401 Unauthorized
│ └─ Tìm thấy ✓
├──▶ Kiểm tra is_locked
│ ├─ Bị khóa → 403 Forbidden
│ └─ Không khóa ✓
├──▶ Kiểm tra is_active
│ ├─ Không active → 403 Forbidden
│ └─ Active ✓
├──▶ Verify password với bcrypt
│ ├─ Sai → Tăng failed_attempts
│ │ ├─ ≥5 lần → Khóa tài khoản
│ │ └─ 401 Unauthorized
│ └─ Đúng ✓
├──▶ Cập nhật thông tin login
│ ├─ Reset failed_attempts = 0
│ ├─ Tăng login_count
│ ├─ Cập nhật last_login
│ ├─ Cập nhật last_login_ip
│ └─ Tạo session_id mới
├──▶ Tạo JWT token
│ └─ Expire sau 24h
└──▶ Return response
└─ { token, user }
[Frontend] Nhận response
├──▶ Lưu token vào localStorage
├──▶ Hiển thị thông báo thành công
└──▶ Sử dụng token cho các request sau
```
## 💡 Best Practices
### Frontend
- ✅ Lưu token trong localStorage
- ✅ Gửi token trong header: `Authorization: Bearer TOKEN`
- ✅ Xử lý token expired (redirect về login)
- ✅ Clear token khi logout
### Backend
- ✅ Validate input (username, password)
- ✅ Rate limiting để chống brute force
- ✅ Log các lần đăng nhập thất bại
- ✅ Use HTTPS trong production
- ✅ Rotate JWT secret định kỳ
### Database
- ✅ Index trên username, email
- ✅ Không bao giờ lưu plain password
- ✅ Use UUID cho ID
- ✅ Soft delete (is_active) thay vì DELETE
## 🔍 Troubleshooting
### "401 Unauthorized"
- Kiểm tra username/password có đúng không
- Kiểm tra user có tồn tại trong DB không
### "403 Forbidden - Account Locked"
- Tài khoản bị khóa do nhập sai password ≥5 lần
- Đợi 30 phút hoặc admin mở khóa
### "Token Invalid"
- Token đã expire (>24h)
- Session đã bị logout
- Đăng nhập lại để lấy token mới
## 📚 Dependencies
- `bcrypt`: Hash passwords
- `jsonwebtoken`: Tạo và verify JWT tokens
- `crypto`: Generate salt và secrets
- `sequelize`: ORM for MySQL
- `express`: Web framework
---
**Created by Sena Team** 🚀