Password Management (Forgot / Reset / Change)
Overview
This feature covers every path a CMS user's password can take: forgetting it and receiving a reset link by email, checking whether a reset code is still valid, setting a new password, and changing the password while logged in — backed by a password history mechanism that blocks reuse of previous passwords.
All the routes sit under /api/user/..., but the code lives in the auth module, not the user
module. When the service was ported from NestJS, the password routes came across together with
the rest of auth. Separately, internal/modules/passwordhistory is a standalone one-for-one port so
that other modules such as user and registration can call it.
Business Flow
Forgot password
- Call
POST /api/user/forgot-passwordwith anemailfield in the body, rate-limited to 3 calls per 10 seconds. - The system generates a random 16-character hex code from 8 bytes of
crypto/randand writes a row intoforgot_passwordwithexpired_dateset to now plus one day, truncating sub-second precision to match the original JavaScript behaviour. - It sends an email through the Mailer, formatting the expiry in the Asia/Bangkok timezone —
for example,
Please reset your password by July 5, 2026 on 5:33 PM (UTC+7). - If no user matches the email address, the system returns error
AUT_101(Not found user for reset password).
Validating the code and setting a new password
GET /api/user/reset-password/:code— cms-web calls this when the emailed link opens, to confirm the code has neither expired nor already been used.POST /api/user/reset-passwordwith the code and the new password hashes it with bcrypt, updatesuser.password, records the change inpassword_history, and marks the code as consumed.
Changing the password while logged in
POST /api/user/change-passwordruns behindJwtAuthand is rate-limited to 5 calls per 60 seconds. It verifies the current password, checks the new one against the history, then updates the password and appends a new history entry.
Key Files & Functions
The main code lives in internal/modules/auth/password.go, internal/modules/auth/hash.go, and
internal/modules/auth/controller.go for route registration. internal/modules/passwordhistory/service.go
is the standalone port, and internal/modules/user/service.go supplies createForgotPassword and
getExpiredDateText.
| Method | Route | Handler | Throttle |
|---|---|---|---|
| POST | /api/user/forgot-password | m.forgotPassword | 3 / 10s |
| POST | /api/user/reset-password | m.resetPassword | 3 / 10s |
| GET | /api/user/reset-password/:code | m.resetPasswordCheckCode | 3 / 10s |
| POST | /api/user/change-password | m.changePassword | 5 / 60s, plus JwtAuth |
Note: internal/modules/passwordhistory/controller.go has an intentionally empty RegisterRoutes,
because the original NestJS controller was empty too — the module is provider-only.
Connections to Other Services
- Permission — none required; these are public routes, except
change-password, which needsJwtAuth. - Tables —
user,forgot_password, andpassword_history. - Mailer —
internal/mail/mail.go, configured from theMAIL_SMTP_*environment variables. - Hashing — bcrypt from
golang.org/x/crypto/bcrypt. - Throttling —
internal/core/middleware/throttle.goviamiddleware.Throttle(limit, ttlMs). - Related pages — Login & JWT, User Management, and Sign-up.