Shared password helper
Problem¶
CMS centralizes bcrypt usage in apps/cms/src/lib/auth/password.ts with cost factor 12. LMS does not — bcrypt is inlined across sign-up, password-reset, and invitation-acceptance flows, and the seed script in apps/lms/scripts/db-seed.ts uses cost 10 instead of 12.
This creates two risks:
- Drift in hashing cost between environments.
- Inconsistent error handling (some call sites throw raw bcrypt errors).
Proposal¶
- Create
apps/lms/src/lib/auth/password.tsmodeled on the CMS helper:hashPassword(plain: string): Promise<string>(cost 12).verifyPassword(plain: string, hash: string): Promise<boolean>.- Throw a domain-specific error rather than the raw bcrypt error.
- Replace inline bcrypt calls in:
apps/lms/src/auth/credentials.ts(and any related auth/action modules).- LMS sign-up, password-reset, and invitation-acceptance server actions.
- apps/lms/scripts/db-seed.ts — also fixes the cost-10 mismatch.
- Add unit tests in
apps/lms/tests/unit/auth/password.test.ts. - This helper will be moved into
@open-learning-hub/server/passwordby T-006 (along with the existing CMS one); landing app-locally first keeps T-006 a pure extraction.
Acceptance criteria¶
- All LMS bcrypt call sites import from
@/lib/auth/password. - No raw
bcrypt.hash/bcrypt.comparecalls remain inapps/lms/src/**orapps/lms/scripts/**. - Seed script uses cost 12.
- New tests pass;
npm run checkpasses.
Out of scope¶
- Migrating existing low-cost hashes in user data (kept as-is; users will be re-hashed on next password change).
- Sharing the helper with CMS (handled by T-006).
- Password policy / strength rules.
Notes / decisions log¶
- 2026-05-24: Ticket created from monorepo audit.
- 2026-05-25: Implemented
apps/lms/src/lib/auth/password.tsmirroring CMS (cost 12, no error wrapping). Replaced inline bcrypt in credentials, sign-up, reset-password, accept-invitation, account actions, andscripts/db-seed.ts(10→12). Addedtests/unit/auth/password.test.ts.npm run checkgreen.