import { betterAuth } from "better-auth"; import { prismaAdapter } from "better-auth/adapters/prisma"; import { genericOAuth } from "better-auth/plugins"; import type { PrismaClient } from "@prisma/client"; /** * Required OIDC environment variables when OIDC is enabled */ const REQUIRED_OIDC_ENV_VARS = ["OIDC_ISSUER", "OIDC_CLIENT_ID", "OIDC_CLIENT_SECRET"] as const; /** * Check if OIDC authentication is enabled via environment variable */ export function isOidcEnabled(): boolean { const enabled = process.env.OIDC_ENABLED; return enabled === "true" || enabled === "1"; } /** * Validates OIDC configuration at startup. * Throws an error if OIDC is enabled but required environment variables are missing. * * @throws Error if OIDC is enabled but required vars are missing or empty */ export function validateOidcConfig(): void { if (!isOidcEnabled()) { // OIDC is disabled, no validation needed return; } const missingVars: string[] = []; for (const envVar of REQUIRED_OIDC_ENV_VARS) { const value = process.env[envVar]; if (!value || value.trim() === "") { missingVars.push(envVar); } } if (missingVars.length > 0) { throw new Error( `OIDC authentication is enabled (OIDC_ENABLED=true) but required environment variables are missing or empty: ${missingVars.join(", ")}. ` + `Either set these variables or disable OIDC by setting OIDC_ENABLED=false.` ); } // Additional validation: OIDC_ISSUER should end with a trailing slash for proper discovery URL const issuer = process.env.OIDC_ISSUER; if (issuer && !issuer.endsWith("/")) { throw new Error( `OIDC_ISSUER must end with a trailing slash (/). Current value: "${issuer}". ` + `The discovery URL is constructed by appending ".well-known/openid-configuration" to the issuer.` ); } } /** * Get OIDC plugins configuration. * Returns empty array if OIDC is disabled, otherwise returns configured OAuth plugin. */ function getOidcPlugins(): ReturnType[] { if (!isOidcEnabled()) { return []; } return [ genericOAuth({ config: [ { providerId: "authentik", clientId: process.env.OIDC_CLIENT_ID ?? "", clientSecret: process.env.OIDC_CLIENT_SECRET ?? "", discoveryUrl: `${process.env.OIDC_ISSUER ?? ""}.well-known/openid-configuration`, scopes: ["openid", "profile", "email"], }, ], }), ]; } export function createAuth(prisma: PrismaClient) { // Validate OIDC configuration at startup - fail fast if misconfigured validateOidcConfig(); return betterAuth({ database: prismaAdapter(prisma, { provider: "postgresql", }), emailAndPassword: { enabled: true, // Enable for now, can be disabled later }, plugins: [...getOidcPlugins()], session: { expiresIn: 60 * 60 * 24, // 24 hours updateAge: 60 * 60 * 24, // 24 hours }, trustedOrigins: [ process.env.NEXT_PUBLIC_APP_URL ?? "http://localhost:3000", "http://localhost:3001", // API origin (dev) "https://app.mosaicstack.dev", // Production web "https://api.mosaicstack.dev", // Production API ], }); } export type Auth = ReturnType;