3d370caa62
Implements Phase 3 from docs/NEXT-STEPS.md. Mongoose schemas (services/provisioning/src/schemas/): - Tenant: slug, name, status, plan, domains, billingInfo, plus handles for Authentik group, OCIS space, and Stalwart domain (set in Phase 4) - User: authentikSubjectId, tenantIds[], email, name, role, platformAdmin flag - Subscription: tenantId, plan, status, Stripe IDs (unused until Phase 4) Auth (services/provisioning/src/auth/): - JwtAuthGuard verifies Authentik access tokens against the provider's JWKS with issuer + audience checks. Uses NODE_EXTRA_CA_CERTS to trust the mkcert root for the local Authentik cert - ActorService resolves the verified JWT into a Mongo User document — every controller reads tenantIds + platformAdmin from the DB, not the token - CurrentUser decorator extracts the JWT payload onto controllers CRUD modules: - /tenants, /users, /subscriptions with create/read/update/delete - /users/me upserts the caller's User record on every request, syncing email, name, tenantIds, and platformAdmin from the JWT's groups claim — the only place we read JWT.groups outside the bootstrap Why DB-derived authz: putting all group memberships in the JWT doesn't scale past ~50 tenants per user (header/cookie size limits, no mid-session revocation, stale data until re-login). JWT now carries identity only; the DB is the source of truth for who can see what. Seed (SeedService.OnApplicationBootstrap): idempotent creation of the default 'dezky' tenant + matching subscription. User records are created on first /users/me hit. Infrastructure: - Traefik label exposes provisioning at https://api.dezky.local (dev only) - api.dezky.local added to Docker network aliases on Traefik - mkcert root CA mounted into the provisioning container for JWKS fetch - Authentik 'groups' scope mapping created + attached to dezky-portal provider; portal now requests it as a scope - nuxt.config.ts portal: exposeAccessToken=true so Nitro forwards token; NUXT_OIDC_TOKEN_KEY fixed to base64-encoded 32 bytes (was hex, causing "Invalid key length" once exposeAccessToken turned on) Portal: apps/portal/server/api/me.get.ts is a scaffolding route that forwards the user's access token to provisioning and returns profile + tenants + subscriptions — verifies the full chain end to end.
28 lines
1.1 KiB
TypeScript
28 lines
1.1 KiB
TypeScript
// Scaffolding route: pulls the signed-in user's profile + tenants + subscriptions
|
|
// from the provisioning service, using the user's Authentik access token forwarded
|
|
// from the encrypted server-side session.
|
|
//
|
|
// Verifies the full chain: portal session → access token → provisioning JWT guard → Mongo.
|
|
|
|
import { getUserSession } from 'nuxt-oidc-auth/runtime/server/utils/session.js'
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
const session = await getUserSession(event).catch(() => null)
|
|
const accessToken = (session as { accessToken?: string } | null)?.accessToken
|
|
|
|
if (!accessToken) {
|
|
throw createError({ statusCode: 401, statusMessage: 'Not signed in or no access token' })
|
|
}
|
|
|
|
const base = process.env.PROVISIONING_INTERNAL_URL ?? 'http://provisioning:3001'
|
|
const headers = { Authorization: `Bearer ${accessToken}` }
|
|
|
|
const [profile, tenants, subscriptions] = await Promise.all([
|
|
$fetch(`${base}/users/me`, { headers }),
|
|
$fetch(`${base}/tenants`, { headers }),
|
|
$fetch(`${base}/subscriptions`, { headers }),
|
|
])
|
|
|
|
return { profile, tenants, subscriptions }
|
|
})
|