diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts index 995b29c..ae2dd87 100644 --- a/app/api/auth/[...nextauth]/route.ts +++ b/app/api/auth/[...nextauth]/route.ts @@ -1,144 +1,5 @@ -import NextAuth, { NextAuthOptions } from "next-auth"; -import KeycloakProvider from "next-auth/providers/keycloak"; - -declare module "next-auth" { - interface Session { - user: { - id: string; - name?: string | null; - email?: string | null; - image?: string | null; - username: string; - first_name: string; - last_name: string; - role: string[]; - }; - accessToken: string; - } - - interface JWT { - accessToken: string; - refreshToken: string; - accessTokenExpires: number; - role: string[]; - username: string; - first_name: string; - last_name: string; - } -} - -function getRequiredEnvVar(name: string): string { - const value = process.env[name]; - if (!value) { - throw new Error(`Missing required environment variable: ${name}`); - } - return value; -} - -export const authOptions: NextAuthOptions = { - providers: [ - KeycloakProvider({ - clientId: getRequiredEnvVar("KEYCLOAK_CLIENT_ID"), - clientSecret: getRequiredEnvVar("KEYCLOAK_CLIENT_SECRET"), - issuer: getRequiredEnvVar("KEYCLOAK_ISSUER"), - profile(profile) { - return { - id: profile.sub, - name: profile.name ?? profile.preferred_username, - email: profile.email, - first_name: profile.given_name ?? '', - last_name: profile.family_name ?? '', - username: profile.preferred_username ?? profile.email?.split('@')[0] ?? '', - role: profile.groups ?? [], - } - }, - }), - ], - session: { - strategy: "jwt", - maxAge: 30 * 24 * 60 * 60, // 30 days - }, - callbacks: { - async jwt({ token, account, profile }) { - if (account && profile) { - token.sub = profile.sub; - token.accessToken = account.access_token; - token.refreshToken = account.refresh_token; - token.accessTokenExpires = account.expires_at! * 1000; - token.role = (profile as any).groups ?? []; - token.username = (profile as any).preferred_username ?? profile.email?.split('@')[0] ?? ''; - token.first_name = (profile as any).given_name ?? ''; - token.last_name = (profile as any).family_name ?? ''; - } - - // Return previous token if not expired - if (Date.now() < (token.accessTokenExpires as number)) { - return token; - } - - try { - const clientId = getRequiredEnvVar("KEYCLOAK_CLIENT_ID"); - const clientSecret = getRequiredEnvVar("KEYCLOAK_CLIENT_SECRET"); - - const response = await fetch( - `${process.env.KEYCLOAK_BASE_URL}/realms/${process.env.KEYCLOAK_REALM}/protocol/openid-connect/token`, - { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - body: new URLSearchParams({ - grant_type: "refresh_token", - client_id: clientId, - client_secret: clientSecret, - refresh_token: token.refreshToken as string, - }), - } - ); - - const tokens = await response.json(); - - if (!response.ok) { - throw new Error("RefreshAccessTokenError"); - } - - return { - ...token, - accessToken: tokens.access_token, - refreshToken: tokens.refresh_token ?? token.refreshToken, - accessTokenExpires: Date.now() + tokens.expires_in * 1000, - }; - } catch (error) { - return { - ...token, - error: "RefreshAccessTokenError", - }; - } - }, - async session({ session, token }) { - if (token.error) { - throw new Error("RefreshAccessTokenError"); - } - - session.accessToken = token.accessToken as string; - session.user = { - ...session.user, - id: token.sub ?? '', - first_name: token.first_name ?? '', - last_name: token.last_name ?? '', - username: token.username ?? '', - role: token.role ?? [], - }; - - return session; - } - }, - pages: { - signIn: '/signin', - error: '/signin', - }, - debug: process.env.NODE_ENV === 'development', -}; +import NextAuth from "next-auth"; +import { authOptions } from "@/lib/auth"; const handler = NextAuth(authOptions); export { handler as GET, handler as POST }; diff --git a/app/signin/page.tsx b/app/signin/page.tsx index 7fb4474..482c779 100644 --- a/app/signin/page.tsx +++ b/app/signin/page.tsx @@ -1,6 +1,6 @@ import { Metadata } from "next"; import { getServerSession } from "next-auth/next"; -import { authOptions } from "@/app/api/auth/[...nextauth]/route"; +import { authOptions } from "@/lib/auth"; import { redirect } from "next/navigation"; import { SignInForm } from "@/components/auth/signin-form";