From 1443d8d9b2a1f0cac4226502b4873ab886a2adcc Mon Sep 17 00:00:00 2001 From: alma Date: Fri, 2 May 2025 13:05:56 +0200 Subject: [PATCH] auth flow --- app/[section]/page.tsx | 4 +- app/api/auth/[...nextauth]/route.ts | 103 +++++++++++++++++++++++++--- app/dossiers/page.tsx | 4 +- 3 files changed, 97 insertions(+), 14 deletions(-) diff --git a/app/[section]/page.tsx b/app/[section]/page.tsx index 2dc1195e..d24fe1c2 100644 --- a/app/[section]/page.tsx +++ b/app/[section]/page.tsx @@ -1,7 +1,7 @@ import { notFound } from 'next/navigation' import { getServerSession } from 'next-auth/next'; import { authOptions } from '@/app/api/auth/[...nextauth]/route'; -import { ResponsiveIframe } from '@/app/components/responsive-iframe'; +import ResponsiveIframe from '@/app/components/responsive-iframe'; import { redirect } from 'next/navigation'; // Use environment variables for real service URLs @@ -62,7 +62,7 @@ export default async function SectionPage(props: { params: { section: string } } ) diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts index 34251d30..80e67910 100644 --- a/app/api/auth/[...nextauth]/route.ts +++ b/app/api/auth/[...nextauth]/route.ts @@ -1,6 +1,57 @@ import NextAuth, { NextAuthOptions } from "next-auth"; import KeycloakProvider from "next-auth/providers/keycloak"; +// Define Keycloak profile type +interface KeycloakProfile { + sub: string; + email?: string; + name?: string; + preferred_username?: string; + given_name?: string; + family_name?: string; + realm_access?: { + roles: string[]; + }; +} + +// Define custom profile type +interface CustomProfile { + id: string; + name?: string | null; + email?: string | null; + username: string; + first_name: string; + last_name: string; + role: string[]; +} + +// Declare module augmentation for NextAuth types +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 { + sub?: string; + accessToken?: string; + refreshToken?: string; + role?: string[]; + username?: string; + first_name?: string; + last_name?: string; + } +} + // Simple, minimal implementation - NO REFRESH TOKEN LOGIC export const authOptions: NextAuthOptions = { providers: [ @@ -8,37 +59,69 @@ export const authOptions: NextAuthOptions = { clientId: process.env.KEYCLOAK_CLIENT_ID || "", clientSecret: process.env.KEYCLOAK_CLIENT_SECRET || "", issuer: process.env.KEYCLOAK_ISSUER || "", + profile(profile: any) { + // Extract roles from the profile + const roles = profile.realm_access?.roles || []; + + return { + id: profile.sub, + name: profile.name || profile.preferred_username, + email: profile.email, + image: null, + role: roles.map((role: string) => role.replace(/^ROLE_/, '').toLowerCase()), + first_name: profile.given_name || '', + last_name: profile.family_name || '', + username: profile.preferred_username || profile.email?.split('@')[0] || '', + }; + } }), ], session: { strategy: "jwt", - maxAge: 8 * 60 * 60, // 8 hours only + maxAge: 8 * 60 * 60, // 8 hours }, callbacks: { - // Simple JWT callback - no refresh logic - async jwt({ token, account }) { - if (account) { - // Initial sign-in, store tokens + async jwt({ token, account, profile }: any) { + if (account && profile) { + // Just store access token and critical fields token.accessToken = account.access_token; - token.sub = account.providerAccountId; + // Make sure roles are available + if (profile.role) { + token.role = profile.role; + token.username = profile.username || ''; + token.first_name = profile.first_name || ''; + token.last_name = profile.last_name || ''; + } } return token; }, - // Simple session callback - async session({ session, token }) { + async session({ session, token }: any) { + // Pass necessary info to the session session.accessToken = token.accessToken; if (session.user) { session.user.id = token.sub || ""; + + // Ensure roles are passed to the session + if (token.role) { + session.user.role = token.role; + session.user.username = token.username || ''; + session.user.first_name = token.first_name || ''; + session.user.last_name = token.last_name || ''; + } else { + // Fallback roles + session.user.role = ["user"]; + session.user.username = ''; + session.user.first_name = ''; + session.user.last_name = ''; + } } return session; } }, - // Redirect to signin page for any errors pages: { signIn: '/signin', error: '/signin', }, - // Set reasonable cookie options cookies: { sessionToken: { name: 'next-auth.session-token', diff --git a/app/dossiers/page.tsx b/app/dossiers/page.tsx index 8610b3ac..0454cd9e 100644 --- a/app/dossiers/page.tsx +++ b/app/dossiers/page.tsx @@ -1,7 +1,7 @@ import { getServerSession } from "next-auth/next"; import { authOptions } from "@/app/api/auth/[...nextauth]/route"; import { redirect } from "next/navigation"; -import { ResponsiveIframe } from "@/app/components/responsive-iframe"; +import ResponsiveIframe from "@/app/components/responsive-iframe"; export default async function Page() { const session = await getServerSession(authOptions); @@ -15,12 +15,12 @@ export default async function Page() {