feat(auth): scaffold Ory Kratos identity service — phase 1 (infrastructure)
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>
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
# Ory Kratos configuration for honeyDue.
|
||||
#
|
||||
# Secrets are NOT in this ConfigMap. The DSN, cookie/cipher secrets, SMTP URI
|
||||
# and OIDC client secrets are injected as environment variables from the
|
||||
# kratos-secrets Secret (see kratos.yaml). Kratos is configured natively via
|
||||
# env vars, so this is the idiomatic split — only non-secret config here.
|
||||
#
|
||||
# OPERATOR: replace the GOOGLE_OAUTH_CLIENT_ID / APPLE_* client-id placeholders
|
||||
# below with the real (non-secret) OAuth client identifiers once the Apple and
|
||||
# Google OAuth apps exist. The matching secrets go in kratos-secrets.
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: kratos-config
|
||||
namespace: honeydue
|
||||
labels:
|
||||
app.kubernetes.io/name: kratos
|
||||
app.kubernetes.io/part-of: honeydue
|
||||
data:
|
||||
kratos.yml: |
|
||||
# version must track the Kratos image tag — confirm against the deployed
|
||||
# Kratos release (Ory uses CalVer, e.g. v26.x). See kratos/README.md.
|
||||
version: v1.3.0
|
||||
|
||||
serve:
|
||||
public:
|
||||
base_url: https://auth.myhoneydue.com/
|
||||
cors:
|
||||
enabled: true
|
||||
allowed_origins:
|
||||
- https://myhoneydue.com
|
||||
- https://app.myhoneydue.com
|
||||
- https://admin.myhoneydue.com
|
||||
allowed_methods: [GET, POST, PUT, PATCH, DELETE]
|
||||
allowed_headers: [Authorization, Content-Type, X-Session-Token, Cookie]
|
||||
exposed_headers: [Content-Type, Set-Cookie]
|
||||
admin:
|
||||
base_url: http://kratos.honeydue.svc.cluster.local:4434/
|
||||
|
||||
selfservice:
|
||||
default_browser_return_url: https://app.myhoneydue.com/
|
||||
allowed_return_urls:
|
||||
- https://app.myhoneydue.com
|
||||
- https://myhoneydue.com
|
||||
- honeydue://callback
|
||||
|
||||
methods:
|
||||
password:
|
||||
enabled: true
|
||||
code: # email one-time codes (verify/recover)
|
||||
enabled: true
|
||||
oidc:
|
||||
enabled: true
|
||||
config:
|
||||
providers:
|
||||
# index 0 — Google. client_secret is injected via env var
|
||||
# SELFSERVICE_METHODS_OIDC_CONFIG_PROVIDERS_0_CLIENT_SECRET.
|
||||
- id: google
|
||||
provider: google
|
||||
client_id: GOOGLE_OAUTH_CLIENT_ID
|
||||
mapper_url: file:///etc/kratos/oidc.google.jsonnet
|
||||
scope: [openid, email, profile]
|
||||
# index 1 — Apple. apple_private_key is injected via env var
|
||||
# SELFSERVICE_METHODS_OIDC_CONFIG_PROVIDERS_1_APPLE_PRIVATE_KEY.
|
||||
- id: apple
|
||||
provider: apple
|
||||
client_id: APPLE_SERVICES_ID
|
||||
apple_team_id: APPLE_TEAM_ID
|
||||
apple_private_key_id: APPLE_PRIVATE_KEY_ID
|
||||
mapper_url: file:///etc/kratos/oidc.apple.jsonnet
|
||||
scope: [openid, email, name]
|
||||
|
||||
flows:
|
||||
error:
|
||||
ui_url: https://app.myhoneydue.com/auth/error
|
||||
login:
|
||||
ui_url: https://app.myhoneydue.com/auth/login
|
||||
lifespan: 10m
|
||||
registration:
|
||||
ui_url: https://app.myhoneydue.com/auth/registration
|
||||
lifespan: 10m
|
||||
after:
|
||||
password:
|
||||
hooks:
|
||||
- hook: session # auto-login after registration
|
||||
oidc:
|
||||
hooks:
|
||||
- hook: session
|
||||
verification:
|
||||
enabled: true
|
||||
ui_url: https://app.myhoneydue.com/auth/verification
|
||||
use: code
|
||||
after:
|
||||
default_browser_return_url: https://app.myhoneydue.com/
|
||||
recovery:
|
||||
enabled: true
|
||||
ui_url: https://app.myhoneydue.com/auth/recovery
|
||||
use: code
|
||||
settings:
|
||||
ui_url: https://app.myhoneydue.com/auth/settings
|
||||
privileged_session_max_age: 15m
|
||||
logout:
|
||||
after:
|
||||
default_browser_return_url: https://app.myhoneydue.com/
|
||||
|
||||
log:
|
||||
level: info
|
||||
format: json
|
||||
leak_sensitive_values: false
|
||||
|
||||
ciphers:
|
||||
algorithm: xchacha20-poly1305
|
||||
|
||||
hashers:
|
||||
algorithm: bcrypt
|
||||
bcrypt:
|
||||
cost: 12
|
||||
|
||||
identity:
|
||||
default_schema_id: honeydue
|
||||
schemas:
|
||||
- id: honeydue
|
||||
url: file:///etc/kratos/identity.schema.json
|
||||
|
||||
courier:
|
||||
smtp:
|
||||
from_address: noreply@myhoneydue.com
|
||||
from_name: honeyDue
|
||||
# connection_uri is injected via env COURIER_SMTP_CONNECTION_URI
|
||||
|
||||
session:
|
||||
lifespan: 720h # 30-day sessions (mobile)
|
||||
cookie:
|
||||
domain: myhoneydue.com
|
||||
same_site: Lax
|
||||
|
||||
identity.schema.json: |
|
||||
{
|
||||
"$id": "https://honeydue.app/identity.schema.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "honeyDue user",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"traits": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
"format": "email",
|
||||
"title": "Email",
|
||||
"minLength": 3,
|
||||
"maxLength": 320,
|
||||
"ory.sh/kratos": {
|
||||
"credentials": {
|
||||
"password": { "identifier": true },
|
||||
"code": { "identifier": true, "via": "email" },
|
||||
"totp": { "account_name": true }
|
||||
},
|
||||
"verification": { "via": "email" },
|
||||
"recovery": { "via": "email" }
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"type": "object",
|
||||
"title": "Name",
|
||||
"properties": {
|
||||
"first": { "type": "string", "title": "First name", "maxLength": 100 },
|
||||
"last": { "type": "string", "title": "Last name", "maxLength": 100 }
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["email"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
oidc.google.jsonnet: |
|
||||
// Maps Google OIDC claims onto the honeyDue identity schema.
|
||||
local claims = std.extVar('claims');
|
||||
{
|
||||
identity: {
|
||||
traits: {
|
||||
email: claims.email,
|
||||
[if 'given_name' in claims || 'family_name' in claims then 'name']: {
|
||||
first: if 'given_name' in claims then claims.given_name else '',
|
||||
last: if 'family_name' in claims then claims.family_name else '',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
oidc.apple.jsonnet: |
|
||||
// Maps Apple OIDC claims onto the honeyDue identity schema. Apple only
|
||||
// returns the name on the very first authorization and not in the ID
|
||||
// token claims, so only email is mapped here.
|
||||
local claims = std.extVar('claims');
|
||||
{
|
||||
identity: {
|
||||
traits: {
|
||||
email: claims.email,
|
||||
},
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user