Diamant

Needs you

  • Inbox
  • Decisions
  • Plans
  • Code

Pipeline

  • Activity
  • Graph
Sign in
Plans/

Passwordless login for the public gateway

Design preview · sample data

Add passwordless email-OTP login to the ITP gateway, restricted to the diamant-software.de domain, as a drop-in step toward Entra SSO.

PBI #1342·Committed·high risk·ITP-Public-Facing-Interface · ITP-Agent-Runtime

Introduce a one-time-code login flow: the gateway issues a 6-digit code by email (Resend), stores a short-lived verification token, and exchanges a valid code for a session. No password storage; domain-gated at issue time.

Why this needs your review

  • Touches authentication and deletes the existing login path.
  • An acceptance criterion (audit logging) is not covered.

Acceptance criteria

2 covered1 partial1 gap
  • A user with a diamant-software.de address receives a 6-digit code by email.

    Resend integration in auth/otp.ts; domain check at issue (step 2).

  • The code expires after 10 minutes and is single-use.

    TTL + consumed-flag on the verification token (step 3).

  • Repeated code requests for one address are rate-limited.

    Partial

    In-memory limiter added — but the threshold is hard-coded, not read from policy.

  • Every login attempt is written to the audit trail.

    Gap

    Not addressed in this plan — no audit write is included.

Implementation · 4 steps

  1. 1

    Create the verification-token table

    Store a hash of the code, an expiry, and a consumed flag — never the raw code.

    src/db/migrations/0042_otp_tokens.sql
  2. 2

    Issue codes via Resend, domain-gated

    Reject non-diamant-software.de addresses before sending. Send a 6-digit code.

    src/auth/otp.tssrc/auth/routes.ts
  3. 3

    Verify and exchange for a session

    Constant-time compare, enforce TTL + single-use, then mint a session.

    src/auth/otp.tssrc/auth/session.ts
  4. 4

    Retire the password path

    Delete password auth and its routes once OTP is the only flow.

    src/auth/password.tssrc/auth/routes.ts

Files · 5

  • +src/auth/otp.tsGenerate, send, and verify one-time codes.
  • ~src/auth/routes.tsAdd POST /otp/request and /otp/verify endpoints.
  • ~src/auth/session.tsExchange a verified code for a session token.
  • +src/db/migrations/0042_otp_tokens.sqlTable for verification tokens (hash, ttl, consumed).
  • −src/auth/password.tsRemove the now-dead password path.

Test plan

  • ·Unit: code generation, TTL expiry, single-use enforcement, constant-time compare.
  • ·Integration: request → email → verify → session happy path.
  • ·Integration: non-domain address is rejected at request time.
  • ·Integration: expired and reused codes are refused.

Risks

  • ·Auth surface — a flaw here is a direct security exposure.
  • ·Removing the password path is irreversible for existing sessions.

Assumptions

  • ·Resend is the approved transactional email provider.
  • ·Session minting already exists and only needs a new entry point.

Open questions

  • ?Should the rate-limit threshold come from the policy ladder rather than a constant?
  • ?Do we need an audit event per attempt for this PBI, or is that a follow-up?

Discussion

Internal — not synced to ADO
  • T
    Tiago1h

    Audit logging is an acceptance criterion — why is it a gap?

  • Plannerbrain1h

    The audit-event schema isn't defined for this surface yet, so I left it out rather than invent one — I flagged it as a gap and an open question. I can add a minimal attempt-log in this run, or split it into a follow-up packet once the schema lands. Which do you want?

  • T
    Tiago14m

    Add the minimal attempt-log in this run.

  • Plan revisedPlanner · 12m

    Added step 5 “Write an attempt-log event” and the 0043 migration; the audit-logging criterion is now covered. Coverage 2 → 3 of 4, no gaps remaining.

The planner replies and revises the plan when it makes sense.