Functional Specification · PCCCTrace Web DN

DN-004 — Quên mật khẩu (Forgot Password)

Đặc tả chức năng cho developer — yêu cầu đặt lại mật khẩu qua link email tự phục hồi, không tiết lộ email tồn tại, kèm trang đặt mật khẩu mới.

Screen IDDN-004
ModuleAuth & Onboarding
ActorMọi user DN quên mật khẩu (pre-auth)
RequirementsFR-6a MVP (scope thu hẹp 03/07/2026), FR-7
Trạng tháiDraft — chờ review
Version / Ngàyv1.1 · 03/07/2026
Màn hình liên quanDN-003 · DN-001
📌 Quyết định scope (PM — 03/07/2026) DN-004 chỉ còn một flow duy nhất: Quên mật khẩu (self-service). Flow "Mất quyền truy cập email" (admin-assisted) đã bỏ; màn DN-005 Đổi Email đã loại bỏ hoàn toàn (ID retired). Email đăng nhập cố định sau đăng ký. Thành viên không tự phục hồi được (mất email) → liên hệ trực tiếp DN Admin của công ty để được đặt lại mật khẩu (row action 🔑 trong DN-008 — chốt 03/07/2026); riêng DN Owner → support@pccctrace.vn.

1.Tổng quan & phạm vi

Mục đích: Cho phép user quên mật khẩu tự phục hồi mà không cần hỗ trợ: nhập email đăng nhập → hệ thống gửi link đặt lại mật khẩu (token một lần, hiệu lực 15 phút) → user mở link, đặt mật khẩu mới → mọi phiên cũ bị đăng xuất → đăng nhập lại tại DN-003.

Trong phạm vi (In scope)

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

2.User story & Acceptance criteria

nhân sự doanh nghiệp PCCC quên mật khẩu đăng nhập,
tôi muốn tự nhận link đặt lại mật khẩu qua email trong vài phút,
để quay lại làm việc ngay mà không phải chờ ai hỗ trợ.
IDAcceptance criteria (Gherkin)
AC-01Given email đã đăng ký, When submit form, Then hệ thống gửi email chứa link đặt lại mật khẩu (token một lần, hiệu lực 15 phút) và hiển thị màn "Kiểm tra hộp thư".
AC-02Given email chưa đăng ký, When submit form, Then hiển thị cùng một màn "Kiểm tra hộp thư" (generic — không tiết lộ email tồn tại) và không gửi gì.
AC-03Given user mở link hợp lệ trong email, When đặt mật khẩu mới đạt yêu cầu (tối thiểu 8 ký tự) và xác nhận khớp, Then mật khẩu cập nhật, mọi phiên và refresh token cũ bị thu hồi, chuyển về DN-003 kèm thông báo thành công.
AC-04Given link đã dùng hoặc quá 15 phút, When mở link, Then hiển thị "Link đã hết hạn hoặc đã sử dụng" + nút yêu cầu link mới (về DN-004).
AC-05Given đã yêu cầu link, When bấm gửi lại trong vòng 60 giây, Then bị chặn bởi cooldown; quá 5 yêu cầu/giờ/email → trả lỗi rate limit.

3.Luồng nghiệp vụ

DN-003 "Quên mật khẩu?" ──► DN-004 nhập email ──► POST /auth/forgot-password │ (luôn trả 202 — generic) ▼ Màn "Kiểm tra hộp thư" (Success state) │ user mở link trong email (TTL 15 phút, 1 lần) ▼ Trang "Đặt mật khẩu mới" (/reset-password?token=...) │ mật khẩu mới ×2 + chỉ báo độ mạnh (tái dùng SU-05 DN-001) ▼ POST /auth/reset-password ──► thu hồi mọi phiên cũ ──► DN-003 + toast thành công

4.UI elements

Layout: card căn giữa, max-width 440px (đồng bộ DN-003), nền --neutral. Tham chiếu trực tiếp prototype DN-004 — dùng đúng design tokens trong tokens.css.

4.1 · Form yêu cầu (mặc định)

IDThành phầnLoạiBắt buộcHành vi / Ghi chú
FP-00Icon 🔑 + headerStaticIcon tròn nền --primary-soft · H1 "Đặt lại mật khẩu" · lead "Nhập email đăng nhập của bạn — chúng tôi sẽ gửi link đặt lại mật khẩu qua email."
FP-01Email đăng nhậpEmail inputtype=email · autocomplete="username" · trim + lowercase trước khi gửi · Enter = submit.
FP-02Nút "Gửi link đặt lại mật khẩu"Button primary, full-widthSubmit → disable + spinner "Đang gửi..." · luôn chuyển sang Success state bất kể email có tồn tại (BR-02).
FP-03Ghi chú gửi emailCallout nhỏ"Link gửi từ noreply@pccctrace.vn, có hiệu lực 15 phút. Không thấy email? Kiểm tra hộp thư Spam."
FP-04Footer cardLink"Nhớ ra mật khẩu rồi? Đăng nhập".
FP-05Topbar linkLink"← Quay lại đăng nhập" → DN-003.

4.2 · Màn "Kiểm tra hộp thư" (Success)

IDThành phầnHành vi / Ghi chú
FP-06Icon 📬 + H1 "Kiểm tra hộp thư của bạn"Thông điệp generic: "Nếu email này đã đăng ký, link đặt lại mật khẩu vừa được gửi tới {email}".
FP-07Dòng gửi lại"Chưa nhận được? Gửi lại sau {n}s" — countdown 60s; hết countdown thành link "Gửi lại" active (tôn trọng BR-03).
FP-08Nút "← Quay lại đăng nhập"Button secondary full-width → DN-003.

4.3 · Trang "Đặt mật khẩu mới" (từ link email — xem OQ-01)

IDThành phầnHành vi / Ghi chú
RP-01Mật khẩu mớiPassword + toggle 👁 + chỉ báo độ mạnh 3 mức — tái dùng đúng component và rule của DN-001 SU-05 (mục 5.1 spec DN-001).
RP-02Xác nhận mật khẩuPhải khớp RP-01, validate on blur + on submit.
RP-03Nút "Đặt lại mật khẩu"Submit → thành công: redirect DN-003 + toast "Mật khẩu đã được đặt lại — vui lòng đăng nhập". Token sai/hết hạn → AC-04.

5.Validation & thông báo lỗi

FieldRuleThông báo lỗi
FP-01Rỗng / sai định dạng email (client)Vui lòng nhập email hợp lệ — banner đỏ + viền đỏ field
FP-01Server: quá 5 yêu cầu/giờ (429)Bạn đã yêu cầu quá nhiều lần. Vui lòng thử lại sau {phút} phút
RP-01<8 ký tựMật khẩu tối thiểu 8 ký tự
RP-02Không khớp RP-01Mật khẩu xác nhận không khớp
TokenServer: hết hạn / đã dùng / không hợp lệLink đã hết hạn hoặc đã được sử dụng. Vui lòng yêu cầu link mới. (+ nút → DN-004)
FormServer: lỗi 5xxToast "Có lỗi xảy ra, vui lòng thử lại" — giữ nguyên dữ liệu đã nhập
Không có lỗi "email không tồn tại" Submit email chưa đăng ký vẫn hiển thị Success state bình thường (AC-02). Đây là hành vi cố ý — chống dò email (user enumeration), đồng bộ BR-04 của DN-003.

6.Business rules

IDQuy tắcNguồn
BR-01Token đặt lại mật khẩu: ngẫu nhiên ≥128 bit, dùng một lần, hiệu lực 15 phút, lưu dạng hash (không lưu plaintext). Tạo token mới → vô hiệu token cũ chưa dùng của cùng user.FR-6a
BR-02API luôn trả 202 generic bất kể email có tồn tại hay không; thời gian phản hồi đồng đều (chống timing attack).FR-7
BR-03Rate limit: cooldown gửi lại 60 giây · tối đa 5 yêu cầu/giờ/email (đồng bộ pattern OTP DN-001 BR-05).FR-7
BR-04Đặt lại mật khẩu thành công → thu hồi toàn bộ phiên + refresh token hiện có của user (chống kẻ chiếm phiên giữ quyền truy cập).BA đề xuất
BR-05Mật khẩu mới hash bcrypt cost 12 — đồng bộ DN-001 BR-07 / DN-003 BR-01. Mật khẩu mới được phép trùng mật khẩu cũ (không giữ lịch sử mật khẩu ở MVP — đơn giản hoá 30 DN).FR-4
BR-08Admin-assisted reset (chốt 03/07/2026): DN Admin/Owner đặt lại mật khẩu cho thành viên role thấp hơn qua DN-008 (🔑): hệ thống tạo mật khẩu tạm ngẫu nhiên hiển thị 1 lần cho Admin chuyển ngoài hệ thống; thành viên buộc đổi mật khẩu ở lần đăng nhập kế; thu hồi mọi phiên cũ như BR-04; ghi audit log (actor_user_id + target_user_id). Owner không tự reset chính mình qua kênh này.PM 03/07/2026
BR-06Ghi audit log: yêu cầu reset (email + IP + timestamp, kể cả email không tồn tại — đánh dấu riêng) và reset thành công (user_id, IP).FR-71
BR-07Email đăng nhập cố định sau đăng ký — không có UI đổi/khôi phục email (quyết định PM 03/07/2026). Thành viên mất quyền truy cập email → liên hệ trực tiếp DN Admin công ty, Admin đặt lại mật khẩu tạm qua DN-008 (xác minh nội bộ doanh nghiệp); DN Owner → support@pccctrace.vn.PM 03/07/2026

7.API contract (đề xuất)

7.1 · Yêu cầu link đặt lại mật khẩu

POST /api/v1/auth/forgot-password
{ "email": "anhtuan@hathanhpccc.vn" }   // lowercase, trim

// 202 Accepted — LUÔN trả giống nhau, có hay không có tài khoản (BR-02)
{ "message": "OK", "resend_cooldown": 60 }

// Lỗi
429 RATE_LIMITED → { "retry_after_seconds": 1800 }   // >5 lần/giờ/email
422 VALIDATION_ERROR → email sai định dạng

7.2 · Đặt mật khẩu mới

POST /api/v1/auth/reset-password
{ "token": "rp_9f3k...", "new_password": "********" }

// 200 OK — mật khẩu đã đổi, mọi phiên cũ bị thu hồi (BR-04)
{ "message": "PASSWORD_RESET_OK" }

// Lỗi
400 TOKEN_INVALID | TOKEN_EXPIRED | TOKEN_USED → "Link đã hết hạn hoặc đã được sử dụng"
422 VALIDATION_ERROR → { "errors": { "new_password": "..." } }

8.Trạng thái màn hình (UI states)

StateMô tả hiển thị
Empty / DefaultForm 1 field email trống (placeholder), nút enable, không lỗi.
Loading (skeleton)Toàn bộ card thay bằng skeleton shimmer khớp layout (icon tròn, title, lead, field, nút, ghi chú, footer) — dùng khi trang đang tải.
ErrorBanner đỏ "Không thể gửi yêu cầu. Vui lòng kiểm tra lại email bên dưới." + viền đỏ field + lỗi inline "Vui lòng nhập email hợp lệ"; giữ nguyên giá trị đã nhập, focus field.
SubmittingNút FP-02 disable + spinner "Đang gửi..." — chặn double-submit.
Success ("Đã gửi link")Icon 📬 "Kiểm tra hộp thư của bạn" + thông điệp generic + dòng "Gửi lại sau {n}s" + nút quay lại đăng nhập.
Khối demo "State & Spec" trong prototype dn-004-recovery-request-mockup.html có khối "State & Spec" ở góc phải dưới: preview Loading / Empty / Error / Đã gửi link (bấm lại nút đang active để về default) + link mở file spec này. Khối này chỉ phục vụ review prototype — không build vào production.

9.Yêu cầu phi chức năng

10.Edge cases

IDTình huốngXử lý
EC-01User yêu cầu nhiều link liên tiếpToken mới nhất vô hiệu mọi token cũ chưa dùng (BR-01); email cũ mở ra sẽ báo hết hạn.
EC-02User đang khoá đăng nhập tạm (sai 5 lần ở DN-003) yêu cầu resetVẫn cho reset bình thường — reset thành công thì gỡ khoá (mật khẩu mới chứng minh sở hữu email).
EC-03Tài khoản thuộc tenant pending_kyc / rejectedVẫn reset được mật khẩu; sau đăng nhập điều hướng theo kyc_status như DN-003 BR-05.
EC-04User mở link trên thiết bị/trình duyệt khác với nơi yêu cầuHoạt động bình thường — token không gắn thiết bị (đơn giản hoá MVP).
EC-05Email scanner của doanh nghiệp tự mở link (pre-fetch) làm token bị "dùng"Token chỉ tính "đã dùng" khi submit mật khẩu mới thành công, không phải khi mở trang (GET không đốt token).
EC-06User quên mật khẩu nhưng cũng mất quyền truy cập emailKhông tự phục hồi được — liên hệ trực tiếp DN Admin công ty để được đặt lại mật khẩu tạm (BR-08; đăng nhập không cần hộp thư). DN Owner rơi vào tình huống này → support@pccctrace.vn.

11.Câu hỏi mở — cần chốt trước khi dev

IDCâu hỏiĐề xuất của BA
OQ-01Trang "Đặt mật khẩu mới" (đích của link email, mục 4.3) chưa có trong screen inventory và chưa có prototype riêng.Thêm màn DN-004b — Đặt mật khẩu mới (route /reset-password) vào inventory + dựng prototype; layout tái dùng card 440px + component mật khẩu của DN-001.
OQ-02DN-010 Recovery Approvals (queue DN Admin duyệt yêu cầu khôi phục) mất nguồn cấp sau khi bỏ flow "mất email" — quên mật khẩu là tự phục hồi, không cần duyệt.✅ Chốt 03/07/2026 (PM): thành viên liên hệ trực tiếp DN Admin (ngoài hệ thống, không có form gửi yêu cầu trong app) → DN-010 loại bỏ, ID retired. Năng lực reset chuyển thành row action 🔑 trong DN-008 Members (BR-08).
OQ-03Nội dung email đặt lại mật khẩu (subject, template) do ai chốt?BA soạn draft cùng template email kết quả KYC (DN-002) thành bộ email transactional thống nhất — 1 file spec riêng.

12.Changelog

VersionNgàyNgườiThay đổi
v1.103/07/2026BAChốt OQ-02 theo quyết định PM: thành viên cần reset mật khẩu (kể cả mất email) liên hệ trực tiếp DN Admin công ty — thêm BR-08 admin-assisted reset qua DN-008 (mật khẩu tạm + buộc đổi + audit); DN-010 loại bỏ, ID retired; cập nhật BR-07, EC-06.
v1.003/07/2026BABản đầu tiên — viết sau quyết định PM 03/07/2026 thu hẹp DN-004 còn flow Quên mật khẩu duy nhất (bỏ flow mất email, loại bỏ DN-005). Wording + layout prototype làm lại theo chuẩn pre-auth 440px; success message generic chống dò email.
← Quay lại mockup