Skip to content

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.tsDEMO_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_PASSWORD in @open-learning-hub/auth-utils/seed-demo-password.
  • getSeedDemoPassword() returns SEED_DEMO_PASSWORD when set, otherwise the canonical default. Does not throw when CI is set.
  • getSeedSuperAdminPassword() prefers SEED_SUPERADMIN_PASSWORD, else getSeedDemoPassword().
  • getTestPassword() returns DEFAULT_SEED_DEMO_PASSWORD only — unit/component tests are env-decoupled and never read CI / 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) documents SEED_DEMO_PASSWORD with 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.mdc dev/test password bullet to reference env vars + the shared helper.

Acceptance criteria

  • Zero shared seed password literals in apps/cms and apps/lms source/tests (docs / templates may reference as example default only; the single source literal lives in DEFAULT_SEED_DEMO_PASSWORD).
  • db:seed in both apps hashes via getSeedDemoPassword() / 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_PASSWORD helper + fixture rewires.
  • 2026-07-17: Follow-up corrections — remove CI-throw (broke unit coverage under CI=true), env-decouple getTestPassword(), drop CI YAML password literals, fix missing LMS e2e DEMO_PASSWORD imports, remove encodeURIComponent from CMS Playwright env, unpin SEED_DEMO_PASSWORD in LMS e2e env merge, relocate thin app re-exports to the shared package import.