17ffd95a70
Upgrade both Nuxt apps to Nuxt 4.4.6 (vue-tsc 3, TypeScript 5.6, undici 7) and add a root tsconfig.json to each app. Fix the strict-null / noUncheckedIndexedAccess errors surfaced by Nuxt 4's stricter generated tsconfig and vue-tsc 3. Drop the nuxt-oidc-auth pnpm patch (Nuxt 4 fixes the prepare:types crash natively).
51 lines
2.4 KiB
TypeScript
51 lines
2.4 KiB
TypeScript
// Forwards to platform-api's POST /flags/evaluate. The platform-api endpoint
|
|
// requires an explicit tenantSlug; we derive it from the JWT here so portal
|
|
// consumers don't have to know which tenant they're "in" — the answer is
|
|
// always the first non-admin group on their token.
|
|
//
|
|
// Caller can override by passing { tenantSlug } in the body, but in practice
|
|
// the portal serves end users with a single tenant in dev.
|
|
|
|
import { getUserSession } from 'nuxt-oidc-auth/runtime/server/utils/session.js'
|
|
|
|
// Group names that belong to platform-wide admin rather than a single tenant.
|
|
// Kept here (not env-driven) because the value is fixed by the Authentik
|
|
// bootstrap script — see services/platform-api/src/users/users.controller.ts
|
|
// where the same name is used as the admin bootstrap group.
|
|
const ADMIN_GROUPS = new Set(['dezky-platform-admins', 'authentik Admins'])
|
|
|
|
function decodeJwtClaims(token: string): Record<string, unknown> {
|
|
const parts = token.split('.')
|
|
if (parts.length < 2) throw new Error('Not a JWT')
|
|
const payload = parts[1]!.replace(/-/g, '+').replace(/_/g, '/')
|
|
const padded = payload + '='.repeat((4 - (payload.length % 4)) % 4)
|
|
return JSON.parse(Buffer.from(padded, 'base64').toString('utf8'))
|
|
}
|
|
|
|
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' })
|
|
|
|
// Allow explicit override; otherwise derive from the JWT.
|
|
const body = (await readBody(event).catch(() => null)) as { tenantSlug?: string } | null
|
|
let tenantSlug = body?.tenantSlug
|
|
if (!tenantSlug) {
|
|
const claims = decodeJwtClaims(accessToken)
|
|
const groups = (claims.groups as string[] | undefined) ?? []
|
|
// Authentik double-lists each group via policy bindings; dedupe + filter.
|
|
const tenantGroups = Array.from(new Set(groups)).filter((g) => !ADMIN_GROUPS.has(g))
|
|
tenantSlug = tenantGroups[0]
|
|
}
|
|
if (!tenantSlug) {
|
|
throw createError({ statusCode: 400, statusMessage: 'No tenant slug available for this user' })
|
|
}
|
|
|
|
const base = process.env.PLATFORM_API_INTERNAL_URL ?? 'http://platform-api:3001'
|
|
return $fetch(`${base}/flags/evaluate`, {
|
|
method: 'POST',
|
|
headers: { Authorization: `Bearer ${accessToken}` },
|
|
body: { tenantSlug },
|
|
})
|
|
})
|