Bỏ qua nội dung

Đăng nhập bằng số điện thoại

Status
shipped
Priority
P0
Owner
team-identity
Platforms
fe · be · mobile
AC progress
8 / 8
Last reviewed
2026-05-22

Mục tiêu

Cho phép user đã có tài khoản đăng nhập bằng số điện thoại + mật khẩu để vào hệ thống. Đây là phương thức đăng nhập chính cho user đăng ký qua flow OTP (auth-register-otp).

Phạm vi

Trong phạm vi (In scope):

  • Đăng nhập bằng số điện thoại VN + mật khẩu đã đặt
  • Trả về access token (JWT, TTL 15 phút) + refresh token (TTL 7 ngày)
  • Cập nhật thời điểm đăng nhập gần nhất (last_login_at) của user
  • Từ chối user có tài khoản bị disabled / locked / banned với thông báo rõ ràng
  • Phân biệt lỗi credentials sai vs lỗi account inactive

Ngoài phạm vi (Out of scope):

  • Đăng nhập bằng OTP (chưa expose endpoint công khai dù backend có method loginByOtp)
  • Đăng nhập bằng email + password
  • Đăng nhập bằng Google OAuth (xem feature auth-login-google)
  • “Remember me” mở rộng refresh token TTL
  • Multi-factor authentication

User Stories

  • user đã đăng ký, tôi muốn nhập số điện thoại + mật khẩu để vào app nhanh chóng.
  • user nhập sai mật khẩu, tôi muốn nhận thông báo lỗi rõ để biết là sai credentials chứ không phải lỗi hệ thống.
  • user bị khoá tài khoản, tôi muốn nhận thông báo “tài khoản không hoạt động” thay vì lỗi generic, để liên hệ admin.

Luồng chức năng

sequenceDiagram
    actor User
    participant App as FE/Mobile
    participant BE

    User->>App: Nhập số điện thoại + mật khẩu
    App->>BE: POST /auth/login {phone, password}
    BE->>BE: Validate format VN + password length
    BE->>BE: Tìm user theo phone

    alt Phone không tồn tại hoặc password sai
        BE-->>App: 401 InvalidCredentialsException
        App->>User: "Số điện thoại hoặc mật khẩu sai"
    else Account disabled / banned / locked
        BE-->>App: 403 AccountNotActiveException
        App->>User: "Tài khoản không hoạt động"
    else Thành công
        BE->>BE: Update last_login_at
        BE->>BE: Sinh access token + refresh token
        BE-->>App: 200 {accessToken, refreshToken, userId, tokenType, expiresIn}
        App->>User: Vào màn hình chính
    end

Acceptance Criteria

  • AC-1: Endpoint POST /auth/login nhận body {phone, password}.
  • AC-2: Phone phải đúng định dạng VN ^0\d{9,10}$; password tối thiểu 6 ký tự, tối đa 64 ký tự.
  • AC-3: Credentials đúng → trả 200 + JSON gồm: accessToken, refreshToken, userId, tokenType (Bearer), expiresIn (giây).
  • AC-4: Credentials sai (phone không tồn tại hoặc password không khớp) → trả 401 với code 4011 (InvalidCredentials), thông điệp generic không tiết lộ là số nào sai hay password sai.
  • AC-5: User có status không phải ACTIVE → từ chối đăng nhập với mã lỗi phân loại (disabled / locked / banned).
  • AC-6: Sau đăng nhập thành công, trường last_login_at của user được cập nhật về thời điểm đăng nhập (UTC).
  • AC-7: Access token là JWT có TTL 15 phút; refresh token có TTL 7 ngày, lưu trong DB và có thể bị revoke.
  • AC-8: Password được hash bằng BCrypt; client phải gửi qua HTTPS — không log password ra cho dù DEBUG.

Quy tắc nghiệp vụ

  • Mật khẩu so khớp qua BCrypt — không bao giờ so plaintext.
  • Lỗi credentials sai phải dùng thông điệp generic (không tiết lộ “số chưa đăng ký” để tránh user enumeration).
  • Lỗi account inactive dùng thông điệp riêng nhưng vẫn không tiết lộ trạng thái chi tiết (disabled vs locked vs banned).
  • Access token và refresh token là 2 entity tách biệt — refresh có thể revoke độc lập (xem feature auth-logout).
  • Mỗi lần đăng nhập tạo refresh token mới (không reuse token cũ).
  • Chỉ tài khoản đăng ký bằng LOCAL mới có password để login flow này dùng. Tài khoản chỉ có Google OAuth không có password → flow này từ chối với credentials sai.

Dữ liệu & Trạng thái

Entity nghiệp vụ:

  • User: phone (định danh để lookup), password hash, status, last_login_at.
  • AccessToken (JWT, không lưu DB): chứa userId, roles, issuer, expiry. Stateless.
  • RefreshToken: token gốc (hash), userId, expiry, revoked flag, rotation chain.

Trạng thái sau đăng nhập:

  • authenticated — có cặp token hợp lệ, có thể gọi API protected.
  • expired-access — access token hết hạn, dùng refresh để rotate (xem feature liên quan refresh — chưa map).
  • revoked — refresh đã bị logout, phải đăng nhập lại.

Phân loại lỗi (HTTP code → mã domain):

  • 401 code 4011 — credentials sai
  • 403 — tài khoản không hoạt động (chi tiết qua message i18n key)
  • 400 — body sai format (phone không phải VN, password rỗng)

Câu hỏi mở

  • ❓ Có nên giới hạn số lần đăng nhập sai trước khi auto-lock tài khoản? (Hiện tại chưa có rate limit theo phone cho login)
  • ❓ Có cần “session expiry warning” thông báo trước khi access token hết hạn?
  • ❓ Có cần multi-device session management (xem danh sách thiết bị đang đăng nhập)?

Liên quan