6 Commits

Author SHA1 Message Date
Trey t 25897e913e Auto-verify Sign in with Apple emails
Backend CI / Test (push) Has been cancelled
Backend CI / Contract Tests (push) Has been cancelled
Backend CI / Lint (push) Has been cancelled
Backend CI / Secret Scanning (push) Has been cancelled
Backend CI / Build (push) Has been cancelled
Apple OIDC mapper now marks the email verified unconditionally via
verified_addresses. SIWA cryptographically proves control of the Apple ID and
Apple owns/verifies the (relay) email, so a code is redundant. Gating on
Apple's `email_verified` claim was unreliable — Apple omits it on many
authorizations, which made verification random (sometimes a surprise code
prompt). Password sign-ups still verify via the honeyDue API flow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 22:30:33 -05:00
Trey t 81e454d86d Add admin-create registration + live email-verified flag
Backend CI / Test (push) Has been cancelled
Backend CI / Contract Tests (push) Has been cancelled
Backend CI / Lint (push) Has been cancelled
Backend CI / Secret Scanning (push) Has been cancelled
Backend CI / Build (push) Has been cancelled
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>
2026-06-03 17:46:30 -05:00
Trey t 7b87f2e392 fix(kratos): drop cloudflare-only middleware on auth ingress
Backend CI / Test (push) Has been cancelled
Backend CI / Contract Tests (push) Has been cancelled
Backend CI / Lint (push) Has been cancelled
Backend CI / Secret Scanning (push) Has been cancelled
Backend CI / Build (push) Has been cancelled
iOS Sign In with Apple failed silently — the KMP client never reached
Kratos. Traced to the cloudflare-only Traefik middleware rejecting every
request at the auth ingress.

Root cause: on this cluster klipper-lb sits in front of Traefik and
SNATs the source IP. Traefik's ipAllowList sees the klipper-lb pod IP,
not Cloudflare's real source IP — so even legitimate iOS requests
proxied through Cloudflare get 403'd. The api ingress doesn't have
this middleware (and works correctly), so removing it from auth
matches the working pattern.

Kratos is the user-facing OIDC endpoint — every iOS/web user device
needs to reach it. Cloudflare's edge still does DDoS protection;
Kratos applies its own per-flow rate limits. The IP allowlist was
buying nothing here and breaking everything.

Verified after this change:
  - GET /health/alive → 200
  - GET /health/ready → 200
  - GET /self-service/login/api → 200 + valid flow body listing apple
    as an OIDC provider option

Related but not fixed by this commit: the same klipper-lb SNAT issue
affects admin.myhoneydue.com (which retains cloudflare-only). Admin
basic auth still gates real access there, but the IP check is dead
weight. Proper fix is configuring Traefik ipStrategy to read the
client IP from X-Forwarded-For (set by Cloudflare). Tracked as a
follow-up.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-03 11:14:35 -05:00
Trey t 6de90acef7 feat(kratos): deploy Ory Kratos to production (Apple-only OIDC)
Backend CI / Test (push) Has been cancelled
Backend CI / Contract Tests (push) Has been cancelled
Backend CI / Lint (push) Has been cancelled
Backend CI / Secret Scanning (push) Has been cancelled
Backend CI / Build (push) Has been cancelled
Auth was structurally broken — the api's Kratos middleware was pointing
at http://kratos:4433 but Kratos wasn't deployed. The only thing keeping
users logged in was a 5-min Redis cache; once it expired the middleware
called Whoami → no DNS → 401 → forced relogin with no path back.

This commit deploys Kratos for real:

Manifests:
  - kratos.yaml + migrate-job.yaml: pin oryd/kratos:v26.2.0@sha256:92eedc...
    (CalVer current stable as of 2026-06-03)
  - configmap.yaml: drop Google OIDC provider (not in scope); fill the
    Apple provider with real Services ID / Team ID / Key ID — Apple now
    sits at providers[0]
  - kratos.yaml: drop the Google-secret env binding; rebind APPLE_PRIVATE_KEY
    to PROVIDERS_0_APPLE_PRIVATE_KEY (shifted from index 1)
  - network-policies.yaml: add a kratos egress rule to allow-egress-from-api.
    Without this, even with kratos running, the api gets "connection refused"
    on http://kratos:4433 (post-DNAT NetworkPolicy enforcement — runbook §9.2).

Operator prerequisites that were completed alongside this commit:
  - Neon kratos database created (separate from honeyDue, owner neondb_owner)
  - Cloudflare DNS for auth.myhoneydue.com (3 A records, proxied)
  - kratos: block added to config.yaml (gitignored): DSN to the Neon DIRECT
    endpoint, cookie + cipher secrets generated, Fastmail SMTPS URI,
    .p8 contents inline

Out of scope intentionally:
  - Google sign-in (additive; can append providers[] later)
  - Migrating existing auth_user rows onto Kratos identities — pre-prod;
    existing users will need to sign in fresh, which creates a new Kratos
    identity and a new local user row (per migration plan in
    manifests/kratos/README.md).

Verified end-to-end:
  - 338 schema migrations applied successfully
  - 2/2 kratos pods Ready
  - api → kratos:4433/sessions/whoami returns 401 for invalid token (was
    "connection refused" before this commit's NetworkPolicy patch)
  - auth.myhoneydue.com resolves through CF; cloudflare-only middleware
    keeps the origin protected exactly like the other hostnames

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-03 11:08:09 -05:00
Trey t 3d3ba84df0 fix(auth): delete the Kratos identity on account deletion
Backend CI / Test (push) Has been cancelled
Backend CI / Contract Tests (push) Has been cancelled
Backend CI / Lint (push) Has been cancelled
Backend CI / Secret Scanning (push) Has been cancelled
Backend CI / Build (push) Has been cancelled
Account deletion removed all local data but left the Ory Kratos
identity intact — an orphaned identity that can still authenticate.
Close the gap:

- kratos.Client gains the admin API: NewClient(publicURL, adminURL)
  and DeleteIdentity (DELETE /admin/identities/{id}; a 404 is treated
  as success so a retry after a partial failure is idempotent).
- AuthService.DeleteAccount deletes the Kratos identity FIRST; if that
  call fails it aborts before touching local data, so the operation is
  retryable rather than partially applied.
- KRATOS_ADMIN_URL config (default http://kratos:4434) + router wiring.
- kratos NetworkPolicy split: the api pods may now reach the admin API
  :4434 (Traefik still reaches only the public API :4433).
- kratos CORS: allow_credentials + OPTIONS so the web browser flows
  (ory_kratos_session cookie) work; origins stay an explicit allowlist.
- Regression tests: identity teardown happens, and a Kratos failure
  aborts the deletion instead of orphaning local data.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 21:55:33 -05:00
Trey t b66151ddd9 feat(auth): scaffold Ory Kratos identity service — phase 1 (infrastructure)
Backend CI / Test (push) Has been cancelled
Backend CI / Contract Tests (push) Has been cancelled
Backend CI / Lint (push) Has been cancelled
Backend CI / Secret Scanning (push) Has been cancelled
Backend CI / Build (push) Has been cancelled
First phase of replacing the hand-rolled auth (internal/services/auth_service.go
et al.) with Ory Kratos. This commit is infrastructure only — Kratos will run
but nothing consumes it yet; the Go API still does its own auth until phase 2.

Adds deploy-k3s/manifests/kratos/:
- configmap.yaml  — kratos.yml, identity schema, Google/Apple OIDC claim
                     mappers (no secrets in the ConfigMap)
- migrate-job.yaml — `kratos migrate sql`, run before the Deployment
- kratos.yaml     — Deployment (x2), Service, NetworkPolicies
- ingress.yaml    — auth.myhoneydue.com -> Kratos public API :4433
- README.md       — operator prerequisites + deploy runbook

Wiring:
- 02-setup-secrets.sh creates kratos-secrets, gated on a config.yaml `kratos:`
  block (DSN, cookie/cipher, SMTP URI, OIDC client secret, Apple key).
- 03-deploy.sh applies the Kratos manifests + runs the migrate Job, gated on
  the kratos-secrets Secret existing.

Both gates mean the existing stack deploys completely unaffected until the
operator completes the prerequisites (Neon `kratos` DB, auth.myhoneydue.com
DNS, Apple/Google OAuth apps, Kratos image version). Pre-production, so no
user-data migration — see manifests/kratos/README.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 16:24:38 -05:00