From 5ac9af018a7428361b684bc7e1b4e53ba44cd6f2 Mon Sep 17 00:00:00 2001 From: jordan Date: Sun, 22 Feb 2026 00:13:12 -0700 Subject: [PATCH] fix: always log OTP codes to stdout in standalone dev mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In-memory auth codes are ephemeral — they're wiped on server restart. Previously, codes were only visible via email delivery. If the server restarted between OTP send and OTP verify, the code would be lost. Now memory.AuthCodeRepository.Create() always logs the code to stdout with a [DEV] prefix. This gives developers a reliable fallback regardless of whether NOTIFY_URL is set. Updated CLAUDE.md to document this behavior and the DEV_USER_EMAIL env var. Co-Authored-By: Claude Sonnet 4.6 --- .../service/internal/adapter/memory/auth_code.go.tmpl | 11 +++++++++++ .../templates/templates/skeleton/CLAUDE.md.tmpl | 3 ++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/internal/adapter/templates/templates/components/service/internal/adapter/memory/auth_code.go.tmpl b/internal/adapter/templates/templates/components/service/internal/adapter/memory/auth_code.go.tmpl index c9bc42e..f60ad1a 100644 --- a/internal/adapter/templates/templates/components/service/internal/adapter/memory/auth_code.go.tmpl +++ b/internal/adapter/templates/templates/components/service/internal/adapter/memory/auth_code.go.tmpl @@ -2,6 +2,7 @@ package memory import ( "context" + "log/slog" "sync" "time" @@ -31,6 +32,16 @@ func (r *AuthCodeRepository) Create(_ context.Context, code *domain.AuthCode) er cp := *code r.codes[code.ID] = &cp + + // In standalone dev mode the code lives only in memory and is lost on restart. + // Always log it so the developer can copy-paste the code from the terminal + // even when NOTIFY_URL is set and an email is also being delivered. + slog.Warn("[DEV] auth code created — use this code to log in", + "email", code.Email, + "purpose", code.Purpose, + "code", code.Code, + "expires_at", code.ExpiresAt.Format("15:04:05"), + ) return nil } diff --git a/internal/adapter/templates/templates/skeleton/CLAUDE.md.tmpl b/internal/adapter/templates/templates/skeleton/CLAUDE.md.tmpl index 7e60593..22a843b 100644 --- a/internal/adapter/templates/templates/skeleton/CLAUDE.md.tmpl +++ b/internal/adapter/templates/templates/skeleton/CLAUDE.md.tmpl @@ -49,7 +49,8 @@ - **No fake progress:** Never simulate progress with timers. Real progress comes from real events. - **Auth tokens:** 15-minute access tokens with embedded session ID (`sid`). Refresh via POST `/auth/refresh`. Session revocation invalidates all tokens for that session. - **Passwords:** Bcrypt cost 12, min 8 chars, max 72. Hashing lives in `pkg/auth/password.go`. Never store plaintext. -- **Auth codes:** OTP/magic link/reset codes are single-use and time-limited. In dev mode (`NOTIFY_URL` unset), codes are logged to stdout. In production, emails go through the notify service (`NOTIFY_URL`/`NOTIFY_API_KEY`/`NOTIFY_HOST`/`NOTIFY_FROM`). +- **Auth codes:** OTP/magic link/reset codes are single-use and time-limited. In standalone dev mode (no `DATABASE_URL`), the code is **always** logged to stdout as `[DEV] auth code created — use this code to log in code=XXXXXX` — check the server terminal to get it. This works even when `NOTIFY_URL` is set. In production (with `DATABASE_URL`), emails go through the notify service (`NOTIFY_URL`/`NOTIFY_API_KEY`/`NOTIFY_HOST`/`NOTIFY_FROM`). +- **Dev user:** Set `DEV_USER_EMAIL=you@example.com` (and optionally `DEV_USER_PASSWORD`) to seed your account into the in-memory store on startup so it survives restarts without re-registering. ## Architecture