Skip to content
FEAT-IDEN-004: Student Recovery Code Acceptance & Session Authorization

FEAT-IDEN-004: Student Recovery Code Acceptance & Session Authorization

[Session-bound recovery under DOM-IDEN-002 §IX]

Reference Number Version Effective Date Supersedes Authority Level Status
FEAT-IDEN-004 1.4 2026-10-01 1.3 Normative ACTIVE

I. Purpose

This FEAT is the second step of the student account recovery workflow. A student who has lost access to their account submits a reset code (generated by a teacher via FEAT-IDEN-003), and the system consumes the code and authorizes only the accepting session to replace credentials. Existing credentials remain intact until setup succeeds.

Per DOM-IDEN-002 §IX (Student Account Recovery) Steps 2-3:

Step 2: “Student submits reset code. Backend validates… Code has not been used, Code has not expired.” Step 3: “Student proceeds through the standard credential setup flow… No identity verification fields (name, DOB, or any PII) are re-entered. All three credentials are fully replaced atomically. Old credentials become invalid immediately.”

This FEAT implements Steps 2-3 of the recovery flow.

Governing Authority:

  • DOM-IDEN-002 §IX (Student Account Recovery)
  • DOM-IDEN-005 §IX (Identity Recovery)
  • FEAT-CORE-000 (Feature Execution Constitutional Directive)

II. Execution Context

0. Precondition: No Authenticated Principal (MANDATORY)

Per DOM-IDEN-005 §VII (v2.3, operator ruling 2026-10-01), recovery, which serves someone who cannot sign in, requires the absence of an authenticated canonical principal. If the request boundary has authenticated a user_id for the session (DOM-IDEN-006 §VIII step 1) — student, teacher or sysadmin, with or without a selected class — the request is refused before this FEAT runs: no Turnstile check, no reset-code lookup or consumption, no lock, and no setup state written. The refusal infers nothing about how the signed-in principal relates to the identity being established. One gate enforces this for every endpoint of the workflow (app/routes/identity_establishment.py); a structural test fails any view that reaches establishment state without being declared there.

1. Required Inputs

  • reset_code: The 8-character alphanumeric code provided by the teacher.
  • idempotency_key: Client-provided unique request ID.

Note: No PII is required. Per DOM-IDEN-002 §IX: “No identity verification fields (name, DOB, or any PII) are re-entered.”

2. Resolved Context (MANDATORY)

Before mutation, the FEAT MUST resolve:

  • student_user: The User record matching the reset code.

No seat, class, IdentityProfile, or participation context may be resolved. Teacher issuance resolves the selected Seat to its User in FEAT-IDEN-003; student redemption and subsequent credential replacement operate on that User alone (DOM-IDEN-002 §IX).


III. Orchestration Logic

A. Verification Phase (Read-Only)

Step 1: Validate Reset Code Input

  1. Verify reset_code is provided and non-empty.
  2. Normalize: Strip whitespace, convert to uppercase.
  3. Failure Behavior: Abort with INVALID_CODE_FORMAT if format is clearly wrong.

Step 2: Find User by Reset Code

  1. Query User where reset_code = reset_code.
  2. Security Constraint: Use generic failure message regardless of outcome (don’t reveal if user exists).
  3. Failure Behavior: Abort with INVALID_OR_EXPIRED_CODE (does not distinguish “code not found” vs “code expired”).

Step 3: Validate Code State

  1. Verify User.reset_code IS NOT NULL (code is present).
  2. Verify User.reset_code_expires_at IS NOT NULL (expiry is set).
  3. Verify NOW() < reset_code_expires_at (code has not expired).
  4. Verify code has not been cleared (would indicate it was already used).
  5. Failure Behavior: Abort with INVALID_OR_EXPIRED_CODE (generic for security).

Step 4: Principal-Only Recovery Boundary

Do not query or select a Seat, class, or IdentityProfile. Recovery authorization comes from the code on the student User; no active or claimed seat is required at redemption. Do not derive a classroom actor from that User.

Step 5: Lock and Revalidate the User

Lock the matching User row for update. Require exactly one match and recheck the code and strict expiry under the lock. Concurrent acceptance has one winner; subsequent attempts fail generically. Hold the lock through the following commit.

B. Mutation Phase (Atomic Transaction)

Step 1: Create Session Authority

Generate a fresh cryptographically random recovery-session nonce. Persist only its verifier in User.recovery_setup_nonce_hash and the original code deadline in User.recovery_setup_expires_at, using SPEC-SEC-001 §V.4. Never log either.

Step 2: Consume the Code

Clear reset_code, reset_code_generated_at, and reset_code_expires_at in the same transaction as creating the server-side recovery-session record. Do not clear or replace credentials or the authenticated-session nonce here. If the transaction fails, neither consumption nor recovery authorization commits.

Step 3: Preserve Participation

Do not read or mutate seats, bindings, profiles, claim artifacts, or class state.

Step 4: Clear Session State (Application-Level)

The route handler SHALL clear recovery-specific session variables:

  • Clear recovery_student_ref
  • Clear any other recovery state

Note: This is application-level housekeeping, not a database mutation.

Step 5: Initialize Credential Setup Session State

The route handler SHALL prepare the credential setup session:

  1. Clear onboarding_seat_ref and stale generated-username/theme state
  2. Set onboarding_user_ref = user_id (for FEAT-IDEN-002)
  3. Set recovery_setup_authorization to the newly generated nonce and use a non-permanent browser session. The User reference alone never authorizes setup.
  4. Every setup request must check the nonce against the User’s current verifier and strict expiry. Session-cookie validity alone is insufficient.

Note: Set session authority only after the database transaction commits. A lost response cannot restore the consumed code; a new teacher-issued code is required.

Step 6: Emit Audit Trace

Per FEAT-CORE-000 §III.4:

  1. Call DOM-OPS to emit ACT-IDEN-004 audit event.
  2. Required fields:
    • feat_id: “FEAT-IDEN-004”
    • user_id: The recovered user_id
    • idempotency_key: The provided key
    • outcome: "RECOVERY_SESSION_AUTHORIZED" (only possible outcome)
    • timestamp: ISO 8601 UTC timestamp

Security Note: DO NOT log the reset code. Log only that acceptance succeeded; never log session authorization material.


IV. Invariants & Constraints

1. Single-Use Reset Code (MANDATORY)

Per DOM-IDEN-002 §IX:

“Reset codes are single-use — consumed when accepted, before credential setup.”

  • Code is consumed atomically with session authorization on acceptance.
  • Attempting to reuse the same code returns INVALID_OR_EXPIRED_CODE.

2. Hard 10-Minute TTL (MANDATORY)

Per DOM-IDEN-002 §IX:

“Hard TTL: 10 minutes.”

  • Code must not be used after reset_code_expires_at.
  • No exceptions, no renewals, no sliding window.

3. Atomic Session Authorization (MANDATORY)

  • Code consumption and server-side nonce creation commit together.
  • Credentials remain intact until FEAT-IDEN-002 successfully replaces them.
  • Completion consumes the nonce atomically with credentials and session rotation.
  • Lost/expired sessions require a new teacher-issued code. No cross-session replay.

4. No PII Requirement (MANDATORY)

Per DOM-IDEN-002 §IX Step 3:

“No identity verification fields (name, DOB, or any PII) are re-entered.”

This FEAT:

  • ✓ Accepts only the reset code
  • ❌ Does NOT ask for name, email, DOB, or any PII
  • ❌ Does NOT ask for student ID or roster matching
  • ❌ Does NOT require answering security questions

5. Generic Error Messages (MANDATORY)

Per DOM-IDEN-002 §IX:

“On failure: return a generic failure message. Do not reveal whether a specific identity exists.”

Always respond with:

  • Valid Code: “Recovery code verified. Please set up your new username and credentials.”
  • Invalid/Expired Code: “Invalid or expired recovery code.” (regardless of reason)

6. Rate Limiting (MANDATORY)

Per DOM-IDEN-002 §IX:

“Rate-limit… reset code… submission.”

Recommended limits:

  • Per IP address: Max 5 attempts per 15 minutes
  • Per reset code: Max 10 total attempts (then it expires or gets locked)
  • Lockout: After 3 failed attempts for a code, add 5-minute wait before next attempt

Exceeding limits returns 429 Too Many Requests.

7. Idempotency (MANDATORY)

  • Idempotency key + reset code ensures replay safety.
  • Retrying a consumed code fails generically, even with the same request key.
  • Audit correlation does not grant or replay recovery authority.
  • Audit log uses key to detect duplicate events.

V. Failure Scenarios

When the FEAT fails, the system SHALL:

  1. Rollback all mutations (code and session authority remain unchanged).
  2. Emit audit event with outcome FAILED and error code.
  3. Return generic error (don’t reveal whether user exists).
  4. NOT advance to credential setup.

Failure Cases:

Scenario Error Code HTTP Status Message
Code not provided INVALID_CODE_FORMAT 400 “Invalid or expired recovery code.”
Code not found CODE_NOT_FOUND 400 “Invalid or expired recovery code.”
Code expired (TTL exceeded) CODE_EXPIRED 400 “Invalid or expired recovery code.”
Code already used CODE_ALREADY_USED 400 “Invalid or expired recovery code.”
Rate limit exceeded RATE_LIMIT_EXCEEDED 429 “Too many attempts. Please wait before trying again.”
Database error INTERNAL_ERROR 500 “An error occurred. Please try again.”
Browser is signed in (an authenticated principal is present) SIGNED_IN 409 (a page load renders the refusal with 200) “We are having trouble determining who you are right now. For your protection, this request was cancelled.” The page offers Return to login, which ends the current sign-in through that role’s logout.

VI. Audit Requirements

The DOM-OPS audit log MUST contain:

Field Type Required Rationale
feat_id String ✓ Identifies FEAT (always “FEAT-IDEN-004”)
user_id Integer ✓ The recovered user
idempotency_key String ✓ Replay detection
outcome Enum ✓ RECOVERY_SESSION_AUTHORIZED or FAILED
timestamp ISO 8601 ✓ UTC timestamp of validation
reset_code String ✗ DO NOT log (security)
error_code String ⚠️ Only if outcome is FAILED

Outcomes (Only One Possible):

  • RECOVERY_SESSION_AUTHORIZED: Code consumed, server-backed session authorized, existing credentials preserved.

VII. Integration with Recovery Flow

FEAT-IDEN-004 is Step 2 of the student recovery workflow:

Step 1: FEAT-IDEN-003 (Teacher Initiates - completed)
└─ Reset code generated, waiting for student

Step 2: FEAT-IDEN-004 (Student Validates Code) ← YOU ARE HERE
├─ Student action: Enter code provided by teacher
├─ System: Consume code and create server-backed session nonce
├─ Old credentials: Preserved until replacement commits
└─ Output: Session state prepared for credential setup

Step 3: FEAT-IDEN-002 (Credential Setup - reused)
├─ Student action: Enter new username, PIN, passphrase
├─ System: Hash and activate new credentials
└─ Output: Student can log in with new credentials

VIII. Session Coordination with FEAT-IDEN-002

After this FEAT succeeds, session state is:

  • onboarding_seat_ref = cleared (recovery has no Seat context)
  • onboarding_user_ref = user_id (tells FEAT-IDEN-002 which user)
  • recovery_student_ref = cleared (no longer needed)
  • recovery_setup_authorization = nonce checked against the server record on every step

FEAT-IDEN-002 validates this state and activates new credentials.


IX. Difference from Initial Claim (FEAT-IDEN-001)

Aspect FEAT-IDEN-001 (Claim) FEAT-IDEN-004 (Recovery)
Purpose First claim of new seat Regain access (lost creds)
User State None yet; FEAT-IDEN-002 creates it Already has old creds
Authorization Name + dedupe code Reset code
Seat New, unclaimed Not resolved
Output seat_id and claim_generation for setup user_id only for setup
Next Step FEAT-IDEN-002 FEAT-IDEN-002

Both workflows funnel to FEAT-IDEN-002: an initial claim to create and bind the User, and recovery to replace credentials.


X. Dependencies

  • docs/FEATURE-EXECUTION/FEAT-CORE-000_FEATURE_EXECUTION_CONSTITUTIONAL_DIRECTIVE.md
  • docs/FEATURE-EXECUTION/FEAT-IDEN-003_TEACHER_RESET_CODE_GENERATION.md
  • docs/FEATURE-EXECUTION/FEAT-IDEN-002_STUDENT_CREDENTIAL_SETUP.md
  • docs/DOMAIN/DOM-IDEN-002_STUDENT_IDENTITY_ARCHITECTURE.md
  • docs/DOMAIN/DOM-IDEN-005_IDENTITY_BINDING_AND_LIFECYCLE.md

XI. Implementation Checklist

Before code review, verify:

  • Generic error message used for all failures (no user existence leaks)
  • Reset code is validated (not expired, exists, not already used)
  • All existing credentials remain unchanged at acceptance
  • Reset code is consumed atomically with server-side nonce creation
  • No Seat, class, or profile is read or mutated during redemption/setup
  • Audit event is emitted with outcome
  • Reset code is NOT logged
  • Rate limiting is enforced (max 5 per 15 min per IP)
  • Lockout occurs after repeated failures (3 attempts + 5 min wait)
  • Concurrent acceptance has one winner; consumed code never authorizes a new session
  • Session state is prepared for FEAT-IDEN-002
  • Tests cover success, expired code, invalid code, and replay scenarios
  • Tests verify generic error messages (no user existence leaks)

XII. Amendments

Revisions to this document SHALL:

  1. Increment the version.
  2. Update the effective date.
  3. Maintain consistency with DOM-IDEN-002 §IX.
  4. Maintain consistency with FEAT-CORE-000.
  5. Maintain consistency with FEAT-IDEN-003 and FEAT-IDEN-002.

Version 1.4 (2026-10-01), operator ruling 2026-10-01: §II.0 makes the absence of an authenticated principal a precondition of code acceptance (DOM-IDEN-005 v2.3 §VII): a signed-in browser is refused without consuming the code. §V adds SIGNED_IN.

Version 1.3 (2026-09-28): §IX follows FEAT-IDEN-001 v3.0 and FEAT-IDEN-002 v2.0. An initial claim has no User until FEAT-IDEN-002 creates one.

Version 1.2 (2026-09-15): consume the code at acceptance and bind reset authority to a server-validated session nonce.

Edit canonical source on GitHub

Last updated: 2026-09-30