// Nuxt 3 configuration for the Dezky operator portal. // Separate app from apps/portal — different OAuth client, different cookies, // different domain, stricter authorization. See docs/OPERATOR-PLAN.md. // Base URLs are environment-driven so one build runs in dev (.local) and // production (.eu) — same approach as apps/portal. Set at BUILD (CI) and // RUNTIME (fleet/apps/operator.yaml + operator-secrets); the .local defaults // keep local dev working with no env. const AUTH_URL = (process.env.NUXT_PUBLIC_AUTH_URL || 'https://auth.dezky.local').replace(/\/$/, '') const OPERATOR_URL = (process.env.NUXT_PUBLIC_OPERATOR_URL || 'https://operator.dezky.local').replace(/\/$/, '') const OPERATOR_OIDC_APP_SLUG = process.env.OPERATOR_OIDC_APP_SLUG || 'dezky-operator' export default defineNuxtConfig({ compatibilityDate: '2026-01-01', devtools: { enabled: true }, modules: ['nuxt-oidc-auth'], runtimeConfig: { public: { // Overridable at runtime via NUXT_PUBLIC_AUTH_URL / NUXT_PUBLIC_OPERATOR_URL // (both set in production). Used for Authentik links and host labels. authUrl: AUTH_URL, operatorUrl: OPERATOR_URL, }, }, css: ['~/assets/styles/tokens.css', '~/assets/styles/base.css'], // Auto-import from the shared packages/ui workspace in addition to the // app's own components/. /shared-packages is bind-mounted in // docker-compose.yml — outside containers the same files live at // /packages/ui/components/. The local dir keeps the default // directory-based prefix; the shared dir uses no prefix so // CountrySelect.vue is just . components: [ '~/components', { path: '/shared-packages/ui/components', pathPrefix: false }, ], app: { head: { htmlAttrs: { 'data-theme': 'dark' }, link: [ { rel: 'preconnect', href: 'https://fonts.googleapis.com' }, { rel: 'preconnect', href: 'https://fonts.gstatic.com', crossorigin: '' }, { rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Inter+Tight:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap', }, ], }, }, oidc: { defaultProvider: 'oidc', session: { expirationCheck: true, automaticRefresh: true, }, middleware: { globalMiddlewareEnabled: true, customLoginPage: true, }, providers: { // Generic OIDC against the dezky-operator Authentik client. Same shape // as the customer portal's config but pointed at a different provider // and a different audience. oidc: { clientId: process.env.NUXT_OIDC_CLIENT_ID || '', clientSecret: process.env.NUXT_OIDC_CLIENT_SECRET || '', redirectUri: process.env.NUXT_OIDC_REDIRECT_URI || `${OPERATOR_URL}/auth/oidc/callback`, authorizationUrl: `${AUTH_URL}/application/o/authorize/`, tokenUrl: `${AUTH_URL}/application/o/token/`, userInfoUrl: `${AUTH_URL}/application/o/userinfo/`, logoutUrl: `${AUTH_URL}/application/o/${OPERATOR_OIDC_APP_SLUG}/end-session/`, openIdConfiguration: `${AUTH_URL}/application/o/${OPERATOR_OIDC_APP_SLUG}/.well-known/openid-configuration`, scope: ['openid', 'profile', 'email', 'groups'], userNameClaim: 'preferred_username', responseType: 'code', grantType: 'authorization_code', pkce: true, skipAccessTokenParsing: true, exposeAccessToken: true, // Also expose id_token so /api/auth/sign-out can pass it as // id_token_hint to Authentik's end-session endpoint. Without it // Authentik can't identify the session to terminate and falls back // to its own "you've logged out" confirmation page. exposeIdToken: true, }, }, }, vite: { server: { // Vite 7 added a strict host check; allow Traefik-fronted hostnames in dev allowedHosts: ['operator.dezky.local'], hmr: { protocol: 'wss', clientPort: 443, }, }, }, nitro: { routeRules: { '/api/**': { cors: true }, }, // Persist nuxt-oidc-auth's session store on disk so HMR / dev-server // restarts don't sign operators out. The default memory driver is fine // in prod where one long-running container holds the state. storage: { oidc: { driver: 'fs', base: '.nuxt/oidc-store' }, }, }, })