Standalone Go 1.25 service that resolves Hydra login and consent challenges, fetches identity traits from Kratos, and redirects Synapse clients with the same semantics as the legacy NestJS implementation.
- Public:
/oidc/login,/oidc/consent - Webhook:
/webhooks/kratos/post-login - Health:
/health/ready,/health/live
Webhook endpoint resolves Alkemio identity claims (alkemio_actor_id, alkemio_agent_id) and stores them in identity.metadata_public via Kratos Admin API.
/webhooks/kratos/post-login- Called after verification and login; patches identity via Admin API
Jsonnet payload (configs/kratos/alkemio-claims.jsonnet):
function(ctx) {
identity_id: ctx.identity.id
}Kratos configuration (kratos.yml):
Alkemio creates the user record after email verification, so webhooks are configured for:
- Post-verification: Resolves claims when user completes email verification
- Post-login: Refreshes claims on each login
selfservice:
flows:
verification:
after:
hooks:
- hook: web_hook
config:
url: http://oidc-service:8080/webhooks/kratos/post-login
method: POST
body: file:///etc/config/kratos/alkemio-claims.jsonnet
response:
ignore: false
parse: false # We update via Admin API
login:
after:
# Hooks must be configured per login method
password:
hooks:
- hook: web_hook
config:
url: http://oidc-service:8080/webhooks/kratos/post-login
method: POST
body: file:///etc/config/kratos/alkemio-claims.jsonnet
response:
ignore: false
parse: false # We update via Admin API
oidc:
hooks:
- hook: web_hook
config:
url: http://oidc-service:8080/webhooks/kratos/post-login
method: POST
body: file:///etc/config/kratos/alkemio-claims.jsonnet
response:
ignore: false
parse: falseResolution failures block login (sessions must have claims): HTTP 404 if identity not found, HTTP 500 for other errors.
On session_required/session_invalid, the service redirects browsers to the
Kratos login flow and returns to /oidc/login after authentication.
-
Specification source lives in
contracts/openapi.yamland stays in sync with the handlers underinternal/server/. -
Preview the documentation with Swagger UI by running:
docker run --rm -p 8089:8080 \ -e SWAGGER_JSON=/tmp/openapi.yaml \ -v "$(pwd)/contracts/openapi.yaml:/tmp/openapi.yaml" \ swaggerapi/swagger-uiThen visit
http://localhost:8089in your browser. -
When editing the spec, validate it locally with
docker run --rm -v "$(pwd)/contracts:/tmp" redocly/cli lint /tmp/openapi.yaml.
Minimal required variables (internal addresses):
OIDC_HYDRA_ADMIN_URL(e.g.http://hydra:4445)OIDC_KRATOS_ADMIN_URL(e.g.http://kratos:4434)OIDC_KRATOS_PUBLIC_URL(e.g.http://kratos:4433)OIDC_WEB_BASE_URL(e.g.http://localhost:3000)
Optional:
OIDC_KRATOS_BROWSER_URLfor browser redirects. If unset, the service uses forwarded headers from your reverse proxy.OIDC_LOGIN_RETURN_BASE_URLto override the default${OIDC_WEB_BASE_URL}/oidc/login.
Database (optional, for fast identity resolution):
DATABASE_HOST(default:localhost)DATABASE_PORT(default:5432)DATABASE_USERNAME(default:synapse)DATABASE_PASSWORD(default:synapse)DATABASE_NAME(default:alkemio)DATABASE_TIMEOUT(default:5s)
When database is configured, the service queries the Alkemio database directly for identity mappings (UserID, AgentID) before falling back to the HTTP API. This reduces latency and load on the Alkemio server. If the database is unavailable, the service gracefully falls back to API-only mode.
See configs/env.sample for a documented template.
Build and test:
make build # Build all binaries
make test # Run test suite
make lint # Run linters
make fmt # Format codeCode generation (after modifying SQL queries):
make sqlc # Generate type-safe Go code from SQL
make generate # Run all code generatorsRun the internal coverage CLI before pushing docstring changes to keep the repository above the enforced threshold:
make docstring-coverageThe target builds cmd/docstringcov, prints overall/per-package coverage, and
writes the structured report to docs/coverage.json. The command exits with a
non-zero status if overall coverage or any package drops below the configured
OIDC_DOCSTRING_COVERAGE_THRESHOLD (defaults to 80%). Supply extra flags via
DOCSTRINGCOV_FLAGS when you need to override the threshold or scan a different
root, e.g. make docstring-coverage DOCSTRINGCOV_FLAGS="--threshold 90".