Lewati ke isi

DEC-014 — Credential Lifecycle & Recovery Contract (V1 Freeze)

Item Value
Decision DEC-014
Tranche T0-D / T0-U07
Status FROZEN for V1
Mode Server development /home/scola/odoo
Related CTRL-006, E2E-GAP-12, STAB-007, QG-03/06/08/09/11/16
Inventory T0-D-credential-surface-inventory.md

This document freezes the chosen Scola V1 credential contract and records rejected alternatives. Implementation must not invent surfaces marked excluded.


Chosen V1 contract (summary)

  1. Activation / provisioning: provisioned users receive a random high-entropy temporary password with scola_must_change_password=True, or a recovery token with purpose activation / admin_reset. No shared school defaults (including no GBS/GTS gbs123/gts123).
  2. Public forgot/reset: anti-enumeration — identical success shape for existing, missing, and disabled accounts.
  3. Session revoke on successful public reset, authenticated password change (other sessions), admin reset, and deactivate.
  4. Break-glass / impersonation: EXCLUDED_AND_DENIED on the customer surface.
  5. Password expiry: not invented; EXCLUDED.
  6. Lockout product: not invented; EXCLUDED (disabled account is a separate state).
  7. Credential audit: system-only ACL; no secrets in audit payloads.

A. Initial activation

Chosen

  • Invited/provisioned users must not receive a reusable shared/default password.
  • V1 activation paths:
  • Provisioned user + random temporary password + must_change_password, then forced change via /api/auth/change-password; or
  • Recovery token with purpose activation or admin_reset (hash-at-rest, single-use, TTL, purpose+user bound).
  • Token properties when used: expires; single use; stored as hash only; never logged raw; invalidated after success; cannot activate another account; generic denial on invalid/expired/replayed.
  • After credential establishment, normal login/session contract (T0-B) applies.

Rejected

Alternative Why rejected
Shared school temps (gbs123/gts123 or similar) Predictable, cross-user reusable, violates least surprise and QG secret hygiene
Permanent known default left on account Stale credential authority
Emailing long-lived plaintext without force-change Credential remains reusable without bound lifecycle
Treating SPMB OTP as password-reset activation Different purpose/channel; must stay separate

B. Authenticated password change

Chosen

  • Authenticated user proves current password via Odoo change_password.
  • On success:
  • credential updates atomically;
  • scola_must_change_password cleared;
  • other sessions revoked; current session sid kept;
  • active-role context remains valid only for the retained session after recompute on next auth payload;
  • audit password_change.
  • Min length 8; new ≠ old.

Rejected

Alternative Why rejected
Change without current-credential proof Hijack via stolen session alone without re-auth proof
Leave other sessions alive Stale credential authority after compromise/reset
Capability-free write on res.users for all roles Privilege expansion

C. Forgotten-password recovery

Chosen

  • Public endpoints:
  • POST /api/auth/password-reset/request
  • POST /api/auth/password-reset/confirm
  • FE: /forgot-password, /reset-password.
  • Anti-enumeration: response for existing active, nonexistent, and disabled accounts uses the same success shape (generic message + correlation_id). Existence must not leak via body/code/message.
  • Recovery token: high entropy; single use; TTL (scola.credential.reset_ttl_seconds, default 3600); account + purpose password_reset bound; superseded on re-issue; hash-at-rest; generic failure on invalid/expired/replay.
  • Successful confirm: set password, clear must_change, revoke all sessions, audit.

Rejected

Alternative Why rejected
Distinct “user not found” / “disabled” messages User enumeration
Predictable tokens (uid/timestamp) Forgeable
Storing raw tokens Secret leakage risk
Allowing confirm to mutate role/company Privilege escalation via recovery

D. Administrative reset

Chosen

  • Retained: POST /api/v1/users/<id>/reset-password (capability/admin scoped).
  • Admin never retrieves the user’s existing password.
  • Operation: set server-generated random temporary secret + must_change_password=True + revoke all sessions + audit admin_reset.
  • Caller-chosen / admin-selected passwords are rejected.
  • One-time reveal of generated temp in response only; never log password.
  • Prefer random temp + force change; recovery-token purpose admin_reset remains available in the token service for future channeling without inventing a second admin UX in V1.

Rejected

Alternative Why rejected
Reset to a permanent known school default Shared secret regression
Reset without must_change User can keep attacker-chosen or admin-chosen password without acknowledgement
Reset without session revoke Old sessions retain authority
Unscoped cross-tenant admin reset Tenant isolation violation

E. Locked / disabled account

Chosen

  • Do not equate: disabled user ↔ credential failure ↔ expired token ↔ revoked role ↔ revoked session.
  • Disabled (active=False): soft-deactivate path revokes sessions; public reset request is a noop that still returns the generic success shape.
  • Account lockout (failed-attempt lock + customer unlock) is not a V1 feature — EXCLUDED. Do not invent unlock workflows merely to fill a matrix.

Rejected

Alternative Why rejected
Invent customer lockout/unlock for V1 completeness Scope creep; false security theater without approved policy
Same error text for disable vs wrong password on login May be desirable later; not required here — keep distinct from public reset anti-enumeration contract

F. Password expiry

Chosen

  • No periodic password expiry in V1.
  • Status: EXCLUDED / NOT_IMPLEMENTED.
  • Do not invent cron-based expiry or “password expired” customer journeys.

Rejected

Alternative Why rejected
Add expiry to “look complete” vs DEC backlog No approved business/security requirement; increases support load and false failures

G. Break-glass / impersonation

Chosen

  • Customer-facing generic impersonation / login-as / credential break-glass: EXCLUDED_AND_DENIED.
  • Existing _is_break_glass_admin for school-unit platform mutate is INTERNAL_OPERATIONAL RBAC, not a customer recovery or impersonation surface.
  • If a future provider-only emergency-access mechanism is approved, it must be separate from school roles, with elevated authority, reason, bounded lifetime, audit, tenant scope, identity attribution, and explicit termination. None is created in V1 by this freeze.

Rejected

Alternative Why rejected
Silent login-as for school admins Unattributed privilege; violates DEC intent
Reusing unit break-glass RBAC as credential recovery Confuses ops elevation with account takeover

Cross-cutting rules (normative)

Topic V1 rule
Shared defaults Forbidden on supported paths
Public reset anti-enumeration Required
Session revoke On public reset, change-password (others), admin reset, deactivate
Break-glass / impersonation (customer) EXCLUDED_AND_DENIED
Password expiry EXCLUDED (not invented)
Lockout product EXCLUDED (not invented)
Audit ACL scola.credential.audit.event and recovery tokens: system-only
Audit contents No passwords, raw tokens, session cookies, or full secrets
SPMB OTP Separate from password-reset tokens
Foundation dead reset route Not resurrected; use canonical admin reset

Rejected global alternatives

Proposal Disposition
Rely on Odoo native signup/reset UI as supported Scola lifecycle Rejected — not assumed canonical
Multiple parallel forgot-password APIs Rejected — one public pair only
Foundation /api/v1/foundation/hr/users/reset-password without controller Leave dead / remove from product exposure
Superadmin as normal E2E actor Rejected (Golden Tenant policy)

Approval record

Approver Date Scope Source
Requesting user (conversation approval) 2026-08-10 DEC-014 V1 contract: server-generated temporary credential + must_change; canonical Scola recovery/change flows; native Odoo lifecycle denied; expiry and customer lockout excluded Current conversation

The requesting user explicitly replaced the previously required formal multi-role sign-off with this approval record. No additional names or signatures are inferred.

Legacy sign-off fields (superseded by the approval record above)

Role Name Date Signature
Product
Identity/RBAC
Security
Operations/Support
DPO (recovery data)

Freeze applies to engineering immediately; the approval record above replaces the previous formal-signature requirement in this decision workflow.