You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
security-service is the single front door for identity across all tenants, and Authentik is an implementation detail behind it. That contract is already stated in backend/security/src/providers/authentik/config.ts — "no vendor name leaks past this boundary into the API surface" — but the implementation is single-tenant. This is the work that makes it true for more than one tenant.
Blocks the MendysRobotics silo shipped in #428: the instance, its provider and its database all exist and are correct, but no MendysRobotics login can succeed until this lands.
Current state
Authentik configuration is read straight from process.env at ~12 call sites across 7 files, with a FuzeFront default hard-coded at each:
Defaults such as 'http://localhost:9000/application/o/fuzefront/' and 'fuzefront-enrollment' are literals in the code. providers/authentik/config.ts is the natural seam — it already centralises the browser-facing paths — but every function there is a zero-arg env reader.
Scope
Tenant registry. Host → { issuerUrl, baseUrl, clientId, clientSecret, adminToken, enrollmentFlowSlug, appBaseUrl, googleBrokered }. Two entries to start: app.fuzefront.com → authentik-server:9000; live./marketplace.mendysrobotics.com → authentik-mendys-server:9000. Chart-driven (a values list rendered to a ConfigMap or JSON env), not a code constant.
Per-request resolution. Middleware resolves the tenant from the Host header and puts it on the request. Fail closed — an unrecognised host must not silently fall back to FuzeFront, or a misrouted request would authenticate against the wrong directory. That is the whole boundary.
Thread it through. Replace every process.env.AUTHENTIK_* read above with the resolved tenant. The awkward ones are machine-identity.ts, authentikPassword.ts and accountApi.ts, which are also called from non-request contexts (provisioning scripts, seed jobs) and so cannot simply read a request-scoped value — they need an explicit tenant argument.
Sessions must be tenant-scoped. security-service issues its own JWT/session. A session minted for one tenant must not validate on another — include the tenant in the token and check it, and scope the cookie to the tenant domain. Getting this wrong silently rejoins the two directories that feat(authentik): MendysRobotics identity silo — separate Authentik instance + platform OIDC client #428 exists to separate.
Login mechanisms for the Mendys tenant (decided):
Password + enrollment → server-side flow driver (authentikPassword.ts, Authentik's /api/v3 flow-executor over ClusterIP). The browser never touches Authentik, and no Authentik native paths are routed on the Mendys hosts — unlike app.fuzefront.com, which does route them.
Social → the existing server-brokered path (/api/v1/security/social/google/callback, googleOidc.ts). Must be per-tenant and must never fall back to Authentik's /source/oauth/*. Note securityService.googleBrokered defaults to "false" in the chart today; the Mendys tenant needs "true". Per-tenant Google client credentials are a design question — decide whether tenants share one Google client or each brings its own.
Chart wiring. Per-tenant env/config replacing the flat AUTHENTIK_* block in templates/security.yaml, plus routing /api/v1/security/* and /api/auth/* from the Mendys hosts to security-service.
Out of scope
Anything in the MendysRobotics repo (tracked at izzywdev/MendysRobotics#253), and retiring the FuzeFront-side mendys-datasets provider (staged separately so datasets sign-in is not interrupted).
Acceptance criteria
Unknown Host fails closed with no FuzeFront fallback, covered by a test
No process.env.AUTHENTIK_* read remains outside the tenant registry
A session minted for tenant A is rejected by tenant B, covered by a test
FuzeFront login is provably unchanged (existing Playwright sign-in flow still green)
A MendysRobotics password login and enrollment succeed end to end against authentik_mendys, with no Authentik hostname reaching the browser
Brokered Google works for the Mendys tenant without touching /source/oauth/*
Risk
This refactors the live authentication path for FuzeFront itself, which has a history of subtle auth outages (probe timeouts evicting the IdP, DNS stalls inside login flows, gravatar lookups in request handling). The FuzeFront tenant's behaviour should be provably identical before the second tenant is enabled — the existing Playwright sign-in flow is the gate.
security-service is the single front door for identity across all tenants, and Authentik is an implementation detail behind it. That contract is already stated in
backend/security/src/providers/authentik/config.ts— "no vendor name leaks past this boundary into the API surface" — but the implementation is single-tenant. This is the work that makes it true for more than one tenant.Blocks the MendysRobotics silo shipped in #428: the instance, its provider and its database all exist and are correct, but no MendysRobotics login can succeed until this lands.
Current state
Authentik configuration is read straight from
process.envat ~12 call sites across 7 files, with a FuzeFront default hard-coded at each:services/oidc.ts:52,86ISSUER_URL,CLIENT_ID,BASE_URLservices/authentikPassword.ts:137,158,640BASE_URL,ENROLLMENT_FLOW_SLUG,ADMIN_TOKENservices/machine-identity.ts:35,53,151BASE_URL,ISSUER_URL,CLIENT_IDproviders/authentik/accountApi.ts:27,34BASE_URL,ADMIN_TOKENproviders/authentik/AuthentikIdentityProvider.ts:718ISSUER_URLroutes/auth.ts:370,453ISSUER_URL,ENROLLMENT_FLOW_SLUGroutes/invitations.ts:81ISSUER_URLDefaults such as
'http://localhost:9000/application/o/fuzefront/'and'fuzefront-enrollment'are literals in the code.providers/authentik/config.tsis the natural seam — it already centralises the browser-facing paths — but every function there is a zero-arg env reader.Scope
{ issuerUrl, baseUrl, clientId, clientSecret, adminToken, enrollmentFlowSlug, appBaseUrl, googleBrokered }. Two entries to start:app.fuzefront.com→authentik-server:9000;live./marketplace.mendysrobotics.com→authentik-mendys-server:9000. Chart-driven (a values list rendered to a ConfigMap or JSON env), not a code constant.Hostheader and puts it on the request. Fail closed — an unrecognised host must not silently fall back to FuzeFront, or a misrouted request would authenticate against the wrong directory. That is the whole boundary.process.env.AUTHENTIK_*read above with the resolved tenant. The awkward ones aremachine-identity.ts,authentikPassword.tsandaccountApi.ts, which are also called from non-request contexts (provisioning scripts, seed jobs) and so cannot simply read a request-scoped value — they need an explicit tenant argument.authentikPassword.ts, Authentik's/api/v3flow-executor over ClusterIP). The browser never touches Authentik, and no Authentik native paths are routed on the Mendys hosts — unlikeapp.fuzefront.com, which does route them./api/v1/security/social/google/callback,googleOidc.ts). Must be per-tenant and must never fall back to Authentik's/source/oauth/*. NotesecurityService.googleBrokereddefaults to"false"in the chart today; the Mendys tenant needs"true". Per-tenant Google client credentials are a design question — decide whether tenants share one Google client or each brings its own.AUTHENTIK_*block intemplates/security.yaml, plus routing/api/v1/security/*and/api/auth/*from the Mendys hosts to security-service.Out of scope
Anything in the MendysRobotics repo (tracked at izzywdev/MendysRobotics#253), and retiring the FuzeFront-side
mendys-datasetsprovider (staged separately so datasets sign-in is not interrupted).Acceptance criteria
Hostfails closed with no FuzeFront fallback, covered by a testprocess.env.AUTHENTIK_*read remains outside the tenant registryauthentik_mendys, with no Authentik hostname reaching the browser/source/oauth/*Risk
This refactors the live authentication path for FuzeFront itself, which has a history of subtle auth outages (probe timeouts evicting the IdP, DNS stalls inside login flows, gravatar lookups in request handling). The FuzeFront tenant's behaviour should be provably identical before the second tenant is enabled — the existing Playwright sign-in flow is the gate.
Related: #428 (the silo), #433 (corrected exposure docs), izzywdev/FuzeInfra#421 (the database).
🤖 Generated with Claude Code