Add passwordless email-OTP login to the ITP gateway, restricted to the diamant-software.de domain, as a drop-in step toward Entra SSO.
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
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.
PartialIn-memory limiter added — but the threshold is hard-coded, not read from policy.
Every login attempt is written to the audit trail.
GapNot addressed in this plan — no audit write is included.
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.sqlIssue codes via Resend, domain-gated
Reject non-diamant-software.de addresses before sending. Send a 6-digit code.
src/auth/otp.tssrc/auth/routes.tsVerify and exchange for a session
Constant-time compare, enforce TTL + single-use, then mint a session.
src/auth/otp.tssrc/auth/session.tsRetire the password path
Delete password auth and its routes once OTP is the only flow.
src/auth/password.tssrc/auth/routes.tssrc/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.Audit logging is an acceptance criterion — why is it a gap?
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?
Add the minimal attempt-log in this run.
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.