Files
sena_db_api_layer/LOGIN_GUIDE.md
2026-01-19 09:33:35 +07:00

345 lines
8.7 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 🔐 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** 🚀