feat(apps): make environment URLs prod-ready (env-driven, not hardcoded .local)
ci / typecheck (map[dir:apps/booking name:booking]) (push) Has been cancelled
ci / typecheck (map[dir:apps/portal name:portal]) (push) Has been cancelled
ci / typecheck (map[dir:apps/website name:website]) (push) Has been cancelled
ci / typecheck (map[dir:services/platform-api name:platform-api]) (push) Has been cancelled
ci / test (push) Has been cancelled

The apps were wired for the dev (.local) environment. Drive the base URLs from
env so one build serves dev and prod (.eu):

- portal nuxt.config: OIDC authorization/token/userinfo/discovery URLs +
  redirectUri now derive from NUXT_PUBLIC_AUTH_URL / NUXT_PUBLIC_PORTAL_URL
  (+ PORTAL_OIDC_APP_SLUG); .local defaults keep dev working with no env.
- portal sign-out handler: end-session + post-logout URLs env-driven.
- portal scheduling page: booking base/host from runtimeConfig.public.bookingUrl
  (NUXT_PUBLIC_BOOKING_URL).
- platform-api: tenant mail domain suffix from PLATFORM_TENANT_DOMAIN (dezky.eu
  in prod), defaulting to dezky.local.

(booking needs no change — its only .local ref is the dev-server allowedHosts.)
This commit is contained in:
Ronni Baslund
2026-06-08 22:18:51 +02:00
parent f331e3c1e6
commit 955357a91a
4 changed files with 30 additions and 12 deletions
+14 -5
View File
@@ -1,6 +1,14 @@
// Nuxt 3 configuration for Dezky portal // Nuxt 3 configuration for Dezky portal
// https://nuxt.com/docs/api/configuration/nuxt-config // https://nuxt.com/docs/api/configuration/nuxt-config
// Base URLs are environment-driven so one build runs in dev (.local) and
// production (.eu). NUXT_PUBLIC_AUTH_URL / NUXT_PUBLIC_PORTAL_URL are set at
// BUILD (CI) and RUNTIME (fleet/apps/portal.yaml + portal-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 PORTAL_URL = (process.env.NUXT_PUBLIC_PORTAL_URL || 'https://app.dezky.local').replace(/\/$/, '')
const PORTAL_OIDC_APP_SLUG = process.env.PORTAL_OIDC_APP_SLUG || 'dezky-portal'
export default defineNuxtConfig({ export default defineNuxtConfig({
compatibilityDate: '2026-01-01', compatibilityDate: '2026-01-01',
devtools: { enabled: true }, devtools: { enabled: true },
@@ -40,6 +48,7 @@ export default defineNuxtConfig({
public: { public: {
authUrl: process.env.NUXT_PUBLIC_AUTH_URL, authUrl: process.env.NUXT_PUBLIC_AUTH_URL,
portalUrl: process.env.NUXT_PUBLIC_PORTAL_URL, portalUrl: process.env.NUXT_PUBLIC_PORTAL_URL,
bookingUrl: process.env.NUXT_PUBLIC_BOOKING_URL || 'https://booking.dezky.local',
}, },
}, },
@@ -69,10 +78,10 @@ export default defineNuxtConfig({
// NUXT_OIDC_* per-container; locally we just read them directly. // NUXT_OIDC_* per-container; locally we just read them directly.
clientId: process.env.PORTAL_OIDC_CLIENT_ID || process.env.NUXT_OIDC_CLIENT_ID || '', clientId: process.env.PORTAL_OIDC_CLIENT_ID || process.env.NUXT_OIDC_CLIENT_ID || '',
clientSecret: process.env.PORTAL_OIDC_CLIENT_SECRET || process.env.NUXT_OIDC_CLIENT_SECRET || '', clientSecret: process.env.PORTAL_OIDC_CLIENT_SECRET || process.env.NUXT_OIDC_CLIENT_SECRET || '',
redirectUri: process.env.NUXT_OIDC_REDIRECT_URI || 'https://app.dezky.local/auth/oidc/callback', redirectUri: process.env.NUXT_OIDC_REDIRECT_URI || `${PORTAL_URL}/auth/oidc/callback`,
authorizationUrl: 'https://auth.dezky.local/application/o/authorize/', authorizationUrl: `${AUTH_URL}/application/o/authorize/`,
tokenUrl: 'https://auth.dezky.local/application/o/token/', tokenUrl: `${AUTH_URL}/application/o/token/`,
userInfoUrl: 'https://auth.dezky.local/application/o/userinfo/', userInfoUrl: `${AUTH_URL}/application/o/userinfo/`,
// Logout is handled by our custom /api/auth/sign-out endpoint, not the // Logout is handled by our custom /api/auth/sign-out endpoint, not the
// module's RP-initiated chain. Authentik 2025.10 doesn't reliably // module's RP-initiated chain. Authentik 2025.10 doesn't reliably
// honor `post_logout_redirect_uri` from the provider invalidation // honor `post_logout_redirect_uri` from the provider invalidation
@@ -82,7 +91,7 @@ export default defineNuxtConfig({
logoutUrl: '', logoutUrl: '',
// Discovery URL — used by id_token validation to fetch JWKS + issuer // Discovery URL — used by id_token validation to fetch JWKS + issuer
openIdConfiguration: openIdConfiguration:
'https://auth.dezky.local/application/o/dezky-portal/.well-known/openid-configuration', `${AUTH_URL}/application/o/${PORTAL_OIDC_APP_SLUG}/.well-known/openid-configuration`,
// offline_access asks Authentik for a refresh token. Without it there's // offline_access asks Authentik for a refresh token. Without it there's
// nothing to refresh with, so session.automaticRefresh can't run and the // nothing to refresh with, so session.automaticRefresh can't run and the
// module's refresh() falls back to a full login() redirect on token // module's refresh() falls back to a full login() redirect on token
+3 -2
View File
@@ -49,7 +49,8 @@ const toast = useToast()
const { tenant } = useTenant() const { tenant } = useTenant()
const slug = computed(() => tenant.value?.slug ?? '') const slug = computed(() => tenant.value?.slug ?? '')
const { request } = useApiFetch() const { request } = useApiFetch()
const bookingBase = 'https://booking.dezky.local' const bookingBase = (useRuntimeConfig().public.bookingUrl as string) || 'https://booking.dezky.local'
const bookingHost = bookingBase.replace(/^https?:\/\//, '')
const base = computed(() => `/api/tenants/${slug.value}/scheduling`) const base = computed(() => `/api/tenants/${slug.value}/scheduling`)
@@ -894,7 +895,7 @@ const maskSecret = (s: string) => (s.length > 12 ? `${s.slice(0, 9)}…${s.slice
<template v-if="!hostForm.slug">Used in the public booking link.</template> <template v-if="!hostForm.slug">Used in the public booking link.</template>
<template v-else-if="!slugValid">Lowercase letters, numbers and hyphens (240 chars).</template> <template v-else-if="!slugValid">Lowercase letters, numbers and hyphens (240 chars).</template>
<template v-else-if="slugTaken">{{ hostForm.slug }} is already taken.</template> <template v-else-if="slugTaken">{{ hostForm.slug }} is already taken.</template>
<template v-else>booking.dezky.local/{{ slug }}/{{ hostForm.slug }} · available </template> <template v-else>{{ bookingHost }}/{{ slug }}/{{ hostForm.slug }} · available </template>
</span> </span>
</label> </label>
<label class="field"><Eyebrow>Timezone</Eyebrow> <label class="field"><Eyebrow>Timezone</Eyebrow>
+8 -2
View File
@@ -24,8 +24,14 @@
import { getUserSession, clearUserSession } from 'nuxt-oidc-auth/runtime/server/utils/session.js' import { getUserSession, clearUserSession } from 'nuxt-oidc-auth/runtime/server/utils/session.js'
const END_SESSION = 'https://auth.dezky.local/application/o/dezky-portal/end-session/' // Environment-driven so one build serves dev (.local) and prod (.eu). The
const POST_LOGOUT_REDIRECT = 'https://app.dezky.local/signed-out' // public runtime config carries authUrl/portalUrl (NUXT_PUBLIC_AUTH_URL /
// NUXT_PUBLIC_PORTAL_URL); fall back to the dev hosts.
const AUTH_URL = (process.env.NUXT_PUBLIC_AUTH_URL || 'https://auth.dezky.local').replace(/\/$/, '')
const PORTAL_URL = (process.env.NUXT_PUBLIC_PORTAL_URL || 'https://app.dezky.local').replace(/\/$/, '')
const OIDC_APP_SLUG = process.env.PORTAL_OIDC_APP_SLUG || 'dezky-portal'
const END_SESSION = `${AUTH_URL}/application/o/${OIDC_APP_SLUG}/end-session/`
const POST_LOGOUT_REDIRECT = `${PORTAL_URL}/signed-out`
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
const session = await getUserSession(event).catch(() => ({} as any)) const session = await getUserSession(event).catch(() => ({} as any))
@@ -94,10 +94,12 @@ export class ProvisioningService {
} }
} }
// Maps tenant slug → mail domain. Production should use a real registered // Maps tenant slug → mail domain. The base is environment-driven
// domain (e.g. acme.dezky.com); locally we use the .local hierarchy. // (PLATFORM_TENANT_DOMAIN, e.g. dezky.eu in prod); defaults to the .local
// hierarchy for local dev.
private domainFor(slug: string): string { private domainFor(slug: string): string {
return `${slug}.dezky.local` const base = process.env.PLATFORM_TENANT_DOMAIN || 'dezky.local'
return `${slug}.${base}`
} }
// Best-effort cleanup. Called when a tenant is hard-deleted (not soft-deleted). // Best-effort cleanup. Called when a tenant is hard-deleted (not soft-deleted).