diff --git a/app/login/page.tsx b/app/login/page.tsx index f3e6fbf..ce53014 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -1,7 +1,7 @@ import Image from 'next/image'; import { redirect } from 'next/navigation'; -import { auth, signIn } from '@/lib/auth'; +import { auth, isAuthentikConfigured, signIn } from '@/lib/auth'; export const metadata = { title: 'Connexion — ITA ITO' }; @@ -49,6 +49,16 @@ export default async function LoginPage({

) : null} + {!isAuthentikConfigured ? ( +

+ La connexion n’est pas configurée : AUTH_AUTHENTIK_ID,{' '} + AUTH_AUTHENTIK_SECRET et{' '} + AUTH_AUTHENTIK_ISSUER doivent être renseignés. +

+ ) : (
{ @@ -65,6 +75,7 @@ export default async function LoginPage({ Se connecter avec {providerName}
+ )} ); diff --git a/lib/auth/index.ts b/lib/auth/index.ts index ad72983..3596c7e 100644 --- a/lib/auth/index.ts +++ b/lib/auth/index.ts @@ -19,6 +19,22 @@ import { groupsFromClaim, roleFromGroups, type Role } from './roles'; const ADMIN_GROUP = process.env.AUTHENTIK_ADMIN_GROUP?.trim() || 'horaires-admins'; +/** + * Whether Authentik is configured well enough to sign anyone in. + * + * Auth.js refuses to build a provider that has no issuer, and that refusal + * takes down the whole auth layer — including reading a session that already + * exists. A missing or misspelled variable would therefore lock everyone out + * of an application that is otherwise perfectly healthy, and say so only as a + * stack trace in the logs. Registering the provider only when it can work + * keeps sessions readable and lets the sign-in page explain itself. + */ +export const isAuthentikConfigured = Boolean( + process.env.AUTH_AUTHENTIK_ID && + process.env.AUTH_AUTHENTIK_SECRET && + process.env.AUTH_AUTHENTIK_ISSUER, +); + /** Enough to explain a read-only account, not enough to bloat the cookie. */ const MAX_REPORTED_GROUPS = 20; @@ -45,7 +61,9 @@ declare module '@auth/core/jwt' { } const nextAuth = NextAuth({ - providers: [Authentik({ name: process.env.AUTHENTIK_DISPLAY_NAME?.trim() || 'Loxi' })], + providers: isAuthentikConfigured + ? [Authentik({ name: process.env.AUTHENTIK_DISPLAY_NAME?.trim() || 'Loxi' })] + : [], // The application sits behind a reverse proxy; the forwarded host is the // real one. trustHost: true, diff --git a/playwright.config.ts b/playwright.config.ts index 57b1b80..a7bfb61 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -72,6 +72,12 @@ export default defineConfig({ AUTH_URL: BASE_URL, AUTH_SECRET: E2E_AUTH_SECRET, AUTHENTIK_ADMIN_GROUP: 'horaires-admins', + // Placeholders, never contacted: the suite mints its own session cookie. + // They exist so the provider builds and the sign-in page renders its + // normal button — CI has no .env to inherit these from. + AUTH_AUTHENTIK_ID: 'e2e-client-id', + AUTH_AUTHENTIK_SECRET: 'e2e-client-secret', + AUTH_AUTHENTIK_ISSUER: 'https://auth.example.test/application/o/e2e/', // The translation service is stubbed per-test; never called for real. TRANSLATION_API_URL: '', TRANSLATION_API_KEY: '',