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.
155 lines
7.4 KiB
Markdown
155 lines
7.4 KiB
Markdown
# Next Steps — After Local Stack Is Running
|
|
|
|
Once `./scripts/bootstrap.sh` completes successfully and all services are reachable, here's the development roadmap.
|
|
|
|
## Phase 1: Verify everything works (day 1) — done
|
|
|
|
- [x] `https://app.dezky.local` shows portal landing page (now the new auth design / post-login home)
|
|
- [x] `https://auth.dezky.local` shows Authentik login
|
|
- [x] Log into Authentik as admin *(still using generated `AUTHENTIK_BOOTSTRAP_PASSWORD` from `.env` — rotate before exposing to anyone else)*
|
|
- [x] Follow `docs/AUTHENTIK-SETUP.md` to configure OIDC providers (ocis + dezky-portal)
|
|
- [x] Test OCIS SSO end-to-end (login from `https://files.dezky.local`)
|
|
- [x] Verify Stalwart admin UI loads at `https://mail.dezky.local/login` *(root path 404s — admin SPA is at `/login`)*
|
|
|
|
## Phase 2: Build portal authentication (week 1) — done
|
|
|
|
Goal: Users can log in to the portal via Authentik.
|
|
|
|
- [x] Add `nuxt-oidc-auth` to `apps/portal` (`1.0.0-beta.11`)
|
|
- [x] Configure Authentik as OIDC provider (generic `oidc` preset with explicit URLs + discovery)
|
|
- [x] Implement login/logout flows (`/auth/oidc/login`, `/auth/oidc/logout` from the module)
|
|
- [x] Display logged-in user info on the portal home (`pages/index.vue` uses `useOidcAuth()`)
|
|
- [x] Add protected routes (`globalMiddlewareEnabled: true`; public pages opt out via `definePageMeta({ auth: false })`)
|
|
|
|
### Where things live
|
|
|
|
| Concern | File |
|
|
|---|---|
|
|
| OIDC module config | `apps/portal/nuxt.config.ts` (`oidc` block) |
|
|
| Custom login page | `apps/portal/pages/auth/login.vue` |
|
|
| Error states (expired / disabled) | `apps/portal/pages/auth/{expired,disabled}.vue` |
|
|
| Post-login landing | `apps/portal/pages/index.vue` |
|
|
| Visual shell + tokens | `apps/portal/components/auth/*`, `assets/styles/tokens.css` |
|
|
| Brand mark | `apps/portal/components/NodeMark.vue` |
|
|
|
|
### Dev-mode caveats (clean up before prod)
|
|
|
|
- `skipAccessTokenParsing: true` in the OIDC config — Authentik's access tokens in this setup aren't reliably JWT-parseable; production should re-evaluate
|
|
- `openIdConfiguration` is pinned to the discovery URL because the generic `oidc` preset doesn't ship a default — required for id_token JWKS validation
|
|
- `docker-compose.yml` mounts `infrastructure/docker-compose/certs/mkcert-root.pem` into the portal at `/etc/ssl/mkcert-root.pem` and sets `NODE_EXTRA_CA_CERTS` so Node fetch trusts the mkcert root CA. In prod, replace with real CA-signed certs
|
|
- Traefik has Docker network aliases for `auth.dezky.local`, `app.dezky.local`, etc. so container-to-Authentik fetch resolves inside the network without going through host `/etc/hosts`
|
|
|
|
## Phase 3: Tenant data model (week 1-2) — done
|
|
|
|
- [x] Mongoose schemas in `services/provisioning/src/schemas/` (Tenant, User, Subscription)
|
|
- [x] Tenant: slug, name, status, plan, domains, authentikGroupId, ocisSpaceId, stalwartDomain, billingInfo
|
|
- [x] User: authentikSubjectId, tenantIds[], email, name, role, active, lastLoginAt
|
|
- [x] Subscription: tenantId, plan, status, stripeCustomerId, stripeSubscriptionId, period dates
|
|
- [x] CRUD endpoints behind `JwtAuthGuard` (validates Authentik JWT via JWKS)
|
|
- [x] Group-based authorization: users see only tenants whose slug matches one of their Authentik `groups`; `dezky-platform-admins` group has global access
|
|
- [x] Idempotent seed (`SeedService`) creates the `dezky` tenant + matching subscription on bootstrap
|
|
- [x] Provisioning exposed at `https://api.dezky.local` (Traefik label, dev only) and via internal `http://provisioning:3001`
|
|
- [x] Portal Nitro route at `/api/me` forwards the user's encrypted access token to provisioning — verified end-to-end
|
|
|
|
### Endpoints
|
|
|
|
| Method | Path | Notes |
|
|
|---|---|---|
|
|
| GET | `/health` | open |
|
|
| POST/GET | `/tenants`, `/tenants/:slug` | platform admin to create/delete; tenant members can read+update their own |
|
|
| GET | `/users/me` | upserts the user on first call from JWT claims |
|
|
| GET/POST/PATCH/DELETE | `/users[/:subject]` | platform admin for mutations |
|
|
| GET/POST/PATCH | `/subscriptions[/:slug]` | platform admin for mutations |
|
|
|
|
### Dev-mode caveats (clean up before prod)
|
|
|
|
- `NUXT_OIDC_TOKEN_KEY` must be base64-encoded 32 bytes (`openssl rand -base64 32`) — NOT hex. Module silently fails with "Invalid key length" if wrong
|
|
- Portal config has `exposeAccessToken: true` so Nitro routes can forward the token; token still never reaches the browser
|
|
- The `dezky` group in Authentik is the single tenant for dev. New tenants in Phase 4 need to create matching Authentik groups
|
|
- A `dezky-platform-admins` group doesn't exist yet — for now akadmin's membership in `authentik Admins` does NOT grant platform-admin rights. Create that group if you want admin-only endpoints to work for you
|
|
|
|
## Phase 4: Provisioning automation (week 2-3)
|
|
|
|
Goal: Sign up creates tenant resources across all services.
|
|
|
|
- [ ] Endpoint: `POST /tenants` — creates tenant in MongoDB
|
|
- [ ] Worker: triggers Authentik tenant/group creation via API
|
|
- [ ] Worker: configures Stalwart domain + DKIM via admin API
|
|
- [ ] Worker: creates OCIS space
|
|
- [ ] Worker: emails customer with onboarding info
|
|
|
|
Authentik API examples:
|
|
```typescript
|
|
// Create group (tenant) in Authentik
|
|
await authentikClient.coreGroupsCreate({
|
|
name: tenant.slug,
|
|
attributes: { tenantId: tenant.id, plan: tenant.plan },
|
|
})
|
|
|
|
// Create user
|
|
await authentikClient.coreUsersCreate({
|
|
username: user.email,
|
|
email: user.email,
|
|
name: user.name,
|
|
groups: [authentikGroupId],
|
|
})
|
|
```
|
|
|
|
## Phase 5: Custom webmail (week 3-4)
|
|
|
|
Goal: Branded webmail client using Stalwart's JMAP API.
|
|
|
|
- [ ] Add JMAP client library to portal
|
|
- [ ] Build inbox view in Nuxt
|
|
- [ ] Build compose dialog
|
|
- [ ] Build message view with thread support
|
|
- [ ] Style to match Dezky branding
|
|
|
|
JMAP is a modern JSON-RPC protocol — clean to work with.
|
|
|
|
## Phase 6: Production migration prep (week 4+)
|
|
|
|
When the local stack is solid and you have 2-3 pilot customers interested:
|
|
|
|
- [ ] Order Hetzner AX41-NVMe
|
|
- [ ] Order Storage Box BX11 (Falkenstein)
|
|
- [ ] Enable Hetzner Object Storage (bucket: dezky-ocis-prod)
|
|
- [ ] Build Terraform module for Hetzner provisioning
|
|
- [ ] Build Ansible playbook for bare-metal Stalwart deployment
|
|
- [ ] Set up k3s on the cloud server
|
|
- [ ] Migrate compose to Helm charts
|
|
- [ ] Configure Let's Encrypt via cert-manager
|
|
- [ ] Set up Restic backup jobs to Storage Box + B2
|
|
|
|
## Phase 7: Add Zulip and Jitsi (when chat/video needed)
|
|
|
|
These were excluded from MVP for simplicity. When ready:
|
|
|
|
- [ ] Create `infrastructure/docker-compose/docker-compose.optional.yml`
|
|
- [ ] Add Zulip stack (server + db + worker)
|
|
- [ ] Add Jitsi stack (web + prosody + jicofo + jvb)
|
|
- [ ] Configure OIDC integration with Authentik
|
|
- [ ] Add to portal launcher
|
|
|
|
## Decisions still open
|
|
|
|
These need to be made before public launch:
|
|
|
|
- [ ] Final pricing tiers (MVP, Pro, Enterprise)
|
|
- [ ] dezky.com purchase decision ($3,000 via BrandBucket)
|
|
- [ ] Final logo design (4 directions explored, need to pick one)
|
|
- [ ] Legal entity structure for the new business
|
|
- [ ] DPA (databehandleraftale) template
|
|
- [ ] Customer support process (ticket system choice)
|
|
|
|
## Long-term architecture goals
|
|
|
|
- [ ] Multi-region deployment (Hetzner Falkenstein + Helsinki)
|
|
- [ ] Disaster recovery: cross-DC Restic copies
|
|
- [ ] ISO 27001 certification via Vanta
|
|
- [ ] GDPR Article 30 record of processing activities
|
|
- [ ] SOC 2 (later, for enterprise customers)
|
|
- [ ] Customer-facing status page (Uptime Kuma or cstate)
|
|
- [ ] Public documentation site
|
|
- [ ] Self-service migration tooling from M365
|