Files
gnickensandClaude Opus 5 0526d34e42 Scaffold FastAPI + MySQL + Keycloak service with devcontainer
Sets up the project skeleton:

- FastAPI app factory with lifespan, request-id middleware, and RFC 9457
  problem+json error handlers
- Async SQLAlchemy 2.0 over MySQL (asyncmy), with a constraint naming
  convention in place before the first migration and async Alembic
- Keycloak as a pure resource server: OIDC discovery, cached JWKS with
  rotation-aware refresh, and require_roles dependencies
- Devcontainer running MySQL 8.4 and Keycloak 26.7 as compose siblings,
  with the realm (clients, roles, test users) imported on first boot
- Test suite covering the endpoints plus the token validator itself,
  exercised against a locally generated RSA keypair
- uv packaging, ruff, mypy --strict, pre-commit, Gitea CI, prod Dockerfile

Two Keycloak-in-containers traps are handled explicitly and documented in
the README: the issuer/internal-URL split (the browser sees localhost:8080,
the API sees keycloak:8080) and the audience mapper that stops Keycloak
issuing tokens with aud=account.

The devices resource is a placeholder proving the routing -> auth -> ORM ->
migration path end to end; replace it with the real domain.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-10 12:46:52 -04:00

136 lines
3.9 KiB
JSON

{
"realm": "v2x",
"displayName": "V2X (development)",
"enabled": true,
"sslRequired": "none",
"registrationAllowed": false,
"loginWithEmailAllowed": true,
"duplicateEmailsAllowed": false,
"accessTokenLifespan": 1800,
"ssoSessionIdleTimeout": 3600,
"ssoSessionMaxLifespan": 36000,
"roles": {
"realm": [
{ "name": "admin", "description": "Full administrative access, including deletes." },
{ "name": "operator", "description": "May create and modify resources." },
{ "name": "viewer", "description": "Read-only access." }
]
},
"clients": [
{
"clientId": "v2x-api",
"name": "V2X API (resource server)",
"description": "The FastAPI service. Validates tokens; also holds a service account for machine-to-machine callers.",
"enabled": true,
"protocol": "openid-connect",
"publicClient": false,
"secret": "dev-only-api-secret",
"bearerOnly": false,
"standardFlowEnabled": false,
"implicitFlowEnabled": false,
"directAccessGrantsEnabled": false,
"serviceAccountsEnabled": true,
"fullScopeAllowed": true,
"attributes": {
"access.token.lifespan": "1800"
},
"protocolMappers": [
{
"name": "v2x-api-audience",
"protocol": "openid-connect",
"protocolMapper": "oidc-audience-mapper",
"consentRequired": false,
"config": {
"included.client.audience": "v2x-api",
"id.token.claim": "false",
"access.token.claim": "true",
"introspection.token.claim": "true"
}
}
]
},
{
"clientId": "v2x-swagger",
"name": "V2X Swagger UI",
"description": "Public client used by /docs to run the authorization-code + PKCE flow.",
"enabled": true,
"protocol": "openid-connect",
"publicClient": true,
"standardFlowEnabled": true,
"implicitFlowEnabled": false,
"directAccessGrantsEnabled": true,
"serviceAccountsEnabled": false,
"fullScopeAllowed": true,
"redirectUris": [
"http://localhost:8000/docs/oauth2-redirect",
"http://localhost:8000/*"
],
"webOrigins": [
"http://localhost:8000"
],
"attributes": {
"pkce.code.challenge.method": "S256",
"post.logout.redirect.uris": "http://localhost:8000/*"
},
"protocolMappers": [
{
"name": "v2x-api-audience",
"protocol": "openid-connect",
"protocolMapper": "oidc-audience-mapper",
"consentRequired": false,
"config": {
"included.client.audience": "v2x-api",
"id.token.claim": "false",
"access.token.claim": "true",
"introspection.token.claim": "true"
}
}
]
}
],
"users": [
{
"username": "[email protected]",
"email": "[email protected]",
"firstName": "Ada",
"lastName": "Admin",
"enabled": true,
"emailVerified": true,
"requiredActions": [],
"credentials": [
{ "type": "password", "value": "password", "temporary": false }
],
"realmRoles": ["admin", "operator", "viewer"]
},
{
"username": "[email protected]",
"email": "[email protected]",
"firstName": "Otto",
"lastName": "Operator",
"enabled": true,
"emailVerified": true,
"requiredActions": [],
"credentials": [
{ "type": "password", "value": "password", "temporary": false }
],
"realmRoles": ["operator", "viewer"]
},
{
"username": "[email protected]",
"email": "[email protected]",
"firstName": "Vera",
"lastName": "Viewer",
"enabled": true,
"emailVerified": true,
"requiredActions": [],
"credentials": [
{ "type": "password", "value": "password", "temporary": false }
],
"realmRoles": ["viewer"]
}
]
}