Centralize seed/demo/test passwords via environment variables¶
Problem¶
After the 2026-07-17 password-policy alignment, CMS and LMS hardcoded a shared seed password literal across seeds, e2e, and unit tests (~26 TS/TSX files). Only the system bootstrap path reads env vars today:
| Surface | CMS | LMS |
|---|---|---|
| System super-admin seed | apps/cms/src/lib/db/seed/system.ts reads SEED_SUPERADMIN_EMAIL / SEED_SUPERADMIN_PASSWORD |
apps/lms/scripts/db-seed-system.ts same |
| Full demo seed users | apps/cms/src/lib/db/seed/data.ts — hardcoded passwords |
apps/lms/src/lib/seed/demo-users.ts — DEMO_PASSWORD constant |
| E2E fixtures | CMS SEED in seed-data.ts re-exports hardcoded passwords; many specs inline the string |
LMS e2e re-exports DEMO_PASSWORD via tests/e2e/fixtures/seed-users.ts; some specs inline |
| Unit/component tests | ~4 CMS + ~12 LMS files inlined the shared seed password literal | same |
This creates drift risk (seeds vs tests vs docs) and prevents CI/local teams from rotating dev credentials without editing source.
Design (shipped)¶
- One canonical default:
DEFAULT_SEED_DEMO_PASSWORDin@open-learning-hub/auth-utils/seed-demo-password. getSeedDemoPassword()returnsSEED_DEMO_PASSWORDwhen set, otherwise the canonical default. Does not throw whenCIis set.getSeedSuperAdminPassword()prefersSEED_SUPERADMIN_PASSWORD, elsegetSeedDemoPassword().getTestPassword()returnsDEFAULT_SEED_DEMO_PASSWORDonly — unit/component tests are env-decoupled and never readCI/SEED_DEMO_PASSWORD.- Seeds resolve via getters / runtime helpers (no import-time throw risk).
- E2E specs import seed credentials (
SEED.*/DEMO_PASSWORD); they do not inline password literals. .env.local.template(both apps) documentsSEED_DEMO_PASSWORDwith the canonical default as an example.- CI does not hardcode the password literal; e2e inherits the resolver default (or an optional override).
- Update
.cursor/rules/120-security.mdcdev/test password bullet to reference env vars + the shared helper.
Acceptance criteria¶
- Zero shared seed password literals in
apps/cmsandapps/lmssource/tests (docs / templates may reference as example default only; the single source literal lives inDEFAULT_SEED_DEMO_PASSWORD). -
db:seedin both apps hashes viagetSeedDemoPassword()/getSeedSuperAdminPassword(). - All e2e auth flows use exported seed credentials (imports verified).
- Unit/component tests use
getTestPassword()from@open-learning-hub/auth-utils/seed-demo-password(deterministic, no env). - T-027 drift matrix row updated.
- Validation:
npm run test,npx turbo run typecheck,npm run check, and targeted Playwright auth specs.
Out of scope¶
- Production user passwords (only dev/seed/test surfaces).
- Changing the canonical password policy schema (
newPasswordSchema).
Notes / decisions log¶
- 2026-07-17: Ticket opened after password-policy alignment exposed widespread hardcoded test fixtures; interim literals tracked in T-027 until this ships.
- 2026-07-17: Initial ship of
SEED_DEMO_PASSWORDhelper + fixture rewires. - 2026-07-17: Follow-up corrections — remove CI-throw (broke unit coverage under
CI=true), env-decouplegetTestPassword(), drop CI YAML password literals, fix missing LMS e2eDEMO_PASSWORDimports, removeencodeURIComponentfrom CMS Playwright env, unpinSEED_DEMO_PASSWORDin LMS e2e env merge, relocate thin app re-exports to the shared package import.