Bỏ qua nội dung

Đăng ký tài khoản với số điện thoại (OTP)

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

Mục tiêu

Cho phép user tạo tài khoản mới bằng số điện thoại Việt Nam. Luồng 3 bước với OTP qua SMS đảm bảo số điện thoại thật sự thuộc về user trước khi đặt mật khẩu, giảm tài khoản giả/spam và tránh chiếm số.

Phạm vi

Trong phạm vi (In scope):

  • Đăng ký bằng số điện thoại Việt Nam (định dạng bắt đầu bằng 0, 10–11 chữ số)
  • OTP 6 chữ số gửi qua SMS (provider ESMS hoặc log mode cho dev)
  • 3 bước rõ ràng: request OTP → verify OTP → create password
  • Đặt mật khẩu (8–64 ký tự) ở bước cuối
  • Hỗ trợ mã giới thiệu (affiliate code) tuỳ chọn, áp dụng silently
  • Cấp access token + refresh token (refresh đặt vào cookie HttpOnly) sau khi đăng ký thành công
  • Khởi tạo ví user và quota unlock mặc định

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

  • Đăng ký bằng email
  • Đăng ký bằng OAuth (xem feature auth-login-google)
  • Xác minh số điện thoại quốc tế (ngoài VN)
  • Resend OTP khi cooldown chưa hết
  • Voice OTP (gọi điện đọc mã)

User Stories

  • user mới, tôi muốn đăng ký nhanh bằng số điện thoại để không phải tạo email hay nhớ mật khẩu của bên thứ 3.
  • user vừa nhập số điện thoại, tôi muốn nhận được OTP trong vòng vài giây để không phải chờ lâu.
  • user nhập sai OTP, tôi muốn biết còn bao nhiêu lần thử để chủ động tránh bị khoá.
  • user đăng ký qua link giới thiệu, tôi muốn mã giới thiệu được áp dụng tự động khi tạo tài khoản.

Luồng chức năng

sequenceDiagram
    actor User
    participant App as FE/Mobile
    participant BE
    participant SMS as SMS Provider

    Note over User,SMS: Bước 1 — Yêu cầu OTP
    User->>App: Nhập số điện thoại
    App->>BE: POST /auth/register/request-otp
    BE->>BE: Validate format VN + check số đã đăng ký chưa
    BE->>SMS: Gửi OTP 6 số
    SMS-->>User: Tin nhắn OTP
    BE-->>App: Trả masked phone + expiry + cooldown

    Note over User,SMS: Bước 2 — Xác thực OTP
    User->>App: Nhập 6 số OTP
    App->>BE: POST /auth/register/verify-otp
    BE->>BE: So OTP, đếm số lần sai
    BE-->>App: Registration token (TTL 10 phút)

    Note over User,SMS: Bước 3 — Đặt mật khẩu
    User->>App: Nhập password + confirm + (mã giới thiệu)
    App->>BE: POST /auth/register/create-password
    BE->>BE: Validate token, tạo user, ví, quota
    BE->>BE: (Optional) áp dụng affiliate code
    BE-->>App: Access token + Refresh token (cookie)
    App->>User: Vào màn hình chính (đã đăng nhập)

Acceptance Criteria

  • AC-1: Số điện thoại phải khớp định dạng VN ^0\d{9,10}$ (bắt đầu bằng 0, tổng 10–11 chữ số). Sai format → 400.
  • AC-2: Nếu số đã có tài khoản (đã đăng ký trước đó) → bước request OTP từ chối, không gửi SMS.
  • AC-3: OTP có 6 chữ số, hết hạn sau 5 phút kể từ lúc gửi.
  • AC-4: Cooldown giữa 2 lần request OTP cho cùng số: 60 giây.
  • AC-5: Mỗi số được tối đa 3 OTP request trong 24 giờ; vượt → 429 Rate Limited.
  • AC-6: Nhập sai OTP 5 lần liên tiếp → khoá yêu cầu OTP mới trong 30 phút cho số đó.
  • AC-7: Verify OTP thành công → cấp registration token (UUID, TTL 10 phút) dùng cho bước create-password.
  • AC-8: Create password yêu cầu password + confirm trùng nhau; password 8–64 ký tự.
  • AC-9: Đăng ký thành công → user có status ACTIVE, role USER, auth provider LOCAL, avatar mặc định (sinh từ tên).
  • AC-10: Sau khi đăng ký thành công, hệ thống khởi tạo ví user (balance = 0 credits) và quota unlock cho ngày hiện tại.
  • AC-11: Mã giới thiệu (nếu có) được áp dụng silently — mã sai không làm fail đăng ký.
  • AC-12: Trả response 201 với access token (TTL 15 phút) và refresh token đặt vào cookie HttpOnly (TTL 7 ngày).

Quy tắc nghiệp vụ

  • Số điện thoại phải là số VN; chuẩn hoá nội bộ về định dạng E.164 để lưu, hiển thị lại dạng local.
  • OTP lưu dưới dạng BCrypt hash trong Redis — server không lưu mã gốc.
  • Số điện thoại hiển thị về client luôn được mask (vd 0901****67).
  • Tên hiển thị mặc định: User {4 số cuối điện thoại} (vd User 4567).
  • User chỉ có thể đăng ký bằng phương thức LOCAL (phone + password) hoặc GOOGLE — không cùng lúc đăng ký 2 phương thức từ đầu, nhưng có thể link sau.
  • Anti-abuse: signupIp và signupUserAgent được lưu khi tạo user (cho phân tích sau).

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

Entity nghiệp vụ:

  • User: số điện thoại (định danh chính), mật khẩu hash, tên hiển thị, avatar URL, vai trò, status, auth provider, ngày tạo.
  • OtpRequest (transient, ở Redis): số điện thoại, OTP hash, thời điểm tạo, hết hạn, số lần thử sai.
  • RegistrationToken (transient, ở Redis): token UUID, gắn với số điện thoại đã verify.
  • UserWallet: tự tạo khi đăng ký, balance 0, status ACTIVE, type MAIN.
  • DailyUnlockUsage: tự tạo cho ngày đăng ký, free unlock limit = entitlement mặc định.

Trạng thái OTP request (user-facing):

  • pending — đã gửi OTP, chờ user nhập
  • verified — OTP đúng, đã có registration token
  • expired — OTP quá 5 phút chưa nhập
  • locked — quá 5 lần sai, khoá 30 phút

Trạng thái User (sau đăng ký):

  • ACTIVE — bình thường (default)
  • LOCKED / BANNED — quản trị viên khoá (out of scope feature này)

Câu hỏi mở

  • ❓ Có cần hỗ trợ resend OTP rõ ràng hay user phải đợi cooldown rồi request lại? (Hiện có endpoint /auth/otp/resend riêng — sẽ tách thành feature khác nếu cần)
  • ❓ Limit 3 OTP/24h có quá thấp cho user thật sự cần thử lại nhiều lần không?
  • ❓ Affiliate code anti-abuse rule chi tiết là gì (giới hạn theo IP, theo user invite, …)?

Liên quan