Previously only 2 share-code routes required a verified email; every other
authenticated route (residences, tasks, contractors, documents, notifications,
subscription, users, uploads, media — ~70 routes) accepted an authenticated but
UNVERIFIED user. This inverts the default to verified-by-default.
- router.go: add a `verified` sub-group that applies RequireVerified() ONCE at
the group level, and move all app-data route setups under it. Verification is
now the default; new routes are gated automatically. The authenticated-only
allow-list is just the sign-up surface (/auth/me, /auth/profile, /auth/account).
Public stays: register, health, webhooks, lookups.
- kratos_auth.go: fix a latent bug the gating exposed — the Redis session cache
stored the verified flag for 24h, so a user who verified their email mid-session
was still seen as unverified until the TTL expired (sign up -> verify -> create
residence would 403). Now only a cached verified=true is trusted (verification
is sticky); a cached verified=false re-resolves the live status from Kratos.
- auth_safety_test.go: add RequireVerified unit tests (verified passes,
unverified -> 403, no-user -> 401).
Validated: API gating test (unverified->403, verified->200) + full iOS XCUITest
suite green (211 passed) including the onboarding verify->use-immediately flow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Registration now goes through POST /api/auth/register, which admin-creates the
Kratos identity (unverified email, NO auto-sent code). Kratos self-service
registration never returns the verification flow id, so the client could never
submit the user's code to the right flow; admin creation lets the client own a
single verification flow instead. Also surface the live Kratos verified flag
and fix Apple audience + team IDs.
- kratos.Client.CreateIdentity via admin API; ErrIdentityExists / ErrInvalidCredentials
- AuthService.Register + AuthHandler.Register + public POST /api/auth/register/
- CurrentUser overrides stale user_profile.verified with the live Kratos flag;
UserRepository.MarkVerified mirrors it back
- configmap: additional_id_token_audiences allows the .dev bundle id_token
- fix Apple/APNs team id V3PF3M6B6U -> X86BR9WTLD in .env.example + dev init
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Production is running with no Kratos deployed in-cluster (the deploy
script's kratos-secrets prerequisite isn't satisfied yet — see runbook
§11 #7). That means Whoami calls ALWAYS fail, so any time a user's Redis
session cache expires they get a 401, which the iOS app treats as session
invalid → forced re-login → can't re-authenticate because the same
Whoami is the only way back in.
Two-part mitigation:
1. Bump kratosSessionCacheTTL from 5 minutes to 24 hours. Active users
stay logged in indefinitely; idle users get bounced after a day.
2. Refresh the cache TTL on every successful cache hit (sliding window)
so usage-driven expiry is no longer a cliff at the original TTL.
When Kratos actually comes up:
- revert the TTL constant to a sensible value (1-15 min)
- the sliding-window refresh is fine to keep; it's good UX regardless
Caveat: this papers over the missing Kratos. New sign-ins still cannot
complete because the api needs Kratos to populate the cache the first
time. Real fix is to deploy Kratos.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Delegates all credential management (login, register, password reset,
email verification, social sign-in) to Ory Kratos. The Go API now acts
as a resource server: the new KratosAuth middleware validates sessions
against the Kratos whoami endpoint, writes the local User mirror into
Echo context, and all existing domain handlers continue working
unchanged. Hand-rolled token auth, AuthToken model, apple_auth/
google_auth services, and the auth refresh flow are removed. Tests are
updated to use the fake-token middleware pattern so existing integration
assertions require no rewrite.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>