0005. Hand-rolled password lifecycle (no IDP primitive)
Status: Accepted, known weakness - revisit before relying on this in a security-sensitive deployment
Context
Per ADR 0001, ThunderID is
the identity provider, but its identity.Provider interface exposes only
CreateUser/UpdateUser/DeleteUser/AssignRole - no
temporary-credential, forced-password-change, or self-service-reset
primitive. OpenSchool needed all three: NIC/index-number default
passwords at account creation, a forced first-login password change, and
a self-service "forgot password" flow.
Decision
Build all three at the application level instead of extending the IDP integration:
- Default passwords - a teacher's/guardian's initial password is
their NIC number; a student's is their index number, set via the same
CreateUser/passwordattribute the IDP integration already uses. - Forced first-login change - a local
users.must_change_passwordflag (not an IDP concept), checked byGET /meand enforced by a full-page frontend interstitial. - Self-service reset -
password_reset_tokensstores only a SHA-256 hash of a short-lived (15 min), single-use token.AuthService.ForgotPasswordidentifies the requester by login email plus their on-file secret (NIC for teacher/parent, index number for student - administrators are excluded, since they have no secondary secret on file) before issuing a token.
Consequences
- This is the one place OpenSchool stores anything resembling a credential itself (a hashed reset token), a deliberate, narrow exception to ADR 0001's "no local password storage" - scoped tightly (single-use, 15-minute TTL, hash-only) specifically because it's an exception.
- Known weakness, tracked as Critical in the project's
audit.md(finding C-1):ForgotPasswordcurrently returns the reset token directly in the API response rather than delivering it through a channel only the account owner controls (e.g. email). Combined with the "secret" being a semi-public identifier (NIC numbers and index numbers appear on physical documents and are often known to family or classmates), this meaningfully weakens the verification step's value. This should be treated as a decision to revisit, not a settled design - seeaudit.mdfor the full failure scenario and suggested fix (deliver the token via the account's on-file email instead of the response body). If you're standing up an instance that will hold real student, guardian, or staff data, read this before you rely on self-service reset. - Administrators cannot use self-service reset - by design, since they have no NIC/index-number-equivalent secret on file. An admin who forgets their password needs direct database/operator intervention.