Files
dezky/services/provisioning/package.json
T
Ronni Baslund 3d370caa62 feat(provisioning): tenant data model + CRUD with JWT-validated authz
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.
2026-05-23 21:53:53 +02:00

37 lines
972 B
JSON

{
"name": "@dezky/provisioning",
"version": "0.0.1",
"private": true,
"description": "Dezky tenant provisioning worker — NestJS",
"scripts": {
"build": "nest build",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"test": "jest",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@nestjs/common": "^10.4.0",
"@nestjs/core": "^10.4.0",
"@nestjs/platform-fastify": "^10.4.0",
"@nestjs/config": "^3.3.0",
"@nestjs/mongoose": "^10.1.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"jose": "^5.9.0",
"mongoose": "^8.7.0",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.0"
},
"devDependencies": {
"@nestjs/cli": "^10.4.0",
"@nestjs/testing": "^10.4.0",
"@types/node": "^20.0.0",
"typescript": "^5.5.0",
"ts-node": "^10.9.2"
},
"packageManager": "pnpm@9.12.0"
}