From e9a0dbfc4a063dd1de0dd63ea570563d29ff0727 Mon Sep 17 00:00:00 2001 From: alma Date: Sat, 3 May 2025 13:01:55 +0200 Subject: [PATCH] cleaning hard 2 --- app/api/auth/[...nextauth]/route.ts | 125 ++++++++++++++++++++++++---- 1 file changed, 110 insertions(+), 15 deletions(-) diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts index 7f846362..0ffdf4ed 100644 --- a/app/api/auth/[...nextauth]/route.ts +++ b/app/api/auth/[...nextauth]/route.ts @@ -60,7 +60,32 @@ export const authOptions: NextAuthOptions = { clientSecret: process.env.KEYCLOAK_CLIENT_SECRET || "", issuer: process.env.KEYCLOAK_ISSUER || "", profile(profile: any) { - console.log("Raw Keycloak profile:", profile); + console.log("Raw Keycloak profile:", JSON.stringify(profile, null, 2)); + + // Try to extract all possible roles from the profile + let roles: string[] = []; + + // Get roles from realm_access + if (profile.realm_access && Array.isArray(profile.realm_access.roles)) { + roles = roles.concat(profile.realm_access.roles); + } + + // Get roles from resource_access + if (profile.resource_access) { + for (const client in profile.resource_access) { + if (profile.resource_access[client] && + Array.isArray(profile.resource_access[client].roles)) { + roles = roles.concat(profile.resource_access[client].roles); + } + } + } + + // Get roles from groups if available + if (profile.groups && Array.isArray(profile.groups)) { + roles = roles.concat(profile.groups); + } + + console.log("Extracted roles from profile:", roles); // Just return a simple profile with required fields return { @@ -71,7 +96,7 @@ export const authOptions: NextAuthOptions = { username: profile.preferred_username || profile.email?.split('@')[0] || '', first_name: profile.given_name || '', last_name: profile.family_name || '', - role: profile.realm_access?.roles || ['user'], + role: roles.length > 0 ? roles : ['user'], // Store raw profile data for later processing raw_profile: profile }; @@ -86,12 +111,16 @@ export const authOptions: NextAuthOptions = { async jwt({ token, account, profile, user }: any) { // Initial sign in if (account && account.access_token) { + console.log("FULL USER OBJECT:", JSON.stringify(user, null, 2)); + console.log("FULL ACCOUNT OBJECT:", JSON.stringify(account, null, 2)); + token.accessToken = account.access_token; token.refreshToken = account.refresh_token; // Process the raw profile data if available if (user && user.raw_profile) { const rawProfile = user.raw_profile; + console.log("RAW KEYCLOAK PROFILE:", JSON.stringify(rawProfile, null, 2)); // Extract roles from all possible sources let roles: string[] = []; @@ -105,6 +134,9 @@ export const authOptions: NextAuthOptions = { // Get roles from resource_access if (rawProfile.resource_access) { const clientId = process.env.KEYCLOAK_CLIENT_ID; + console.log("Client ID for resource access:", clientId); + console.log("Resource access object:", JSON.stringify(rawProfile.resource_access, null, 2)); + if (clientId && rawProfile.resource_access[clientId] && Array.isArray(rawProfile.resource_access[clientId].roles)) { @@ -118,6 +150,20 @@ export const authOptions: NextAuthOptions = { roles = roles.concat(rawProfile.resource_access.account.roles); console.log("Roles from resource_access.account:", rawProfile.resource_access.account.roles); } + + // Check for any roles in any client + for (const [clientKey, clientData] of Object.entries(rawProfile.resource_access)) { + if (clientData && Array.isArray((clientData as any).roles)) { + console.log(`Found roles in client ${clientKey}:`, (clientData as any).roles); + roles = roles.concat((clientData as any).roles); + } + } + } + + // Look for roles in other potential locations + if (rawProfile.groups && Array.isArray(rawProfile.groups)) { + console.log("Found groups that might contain roles:", rawProfile.groups); + roles = roles.concat(rawProfile.groups); } // Clean up roles and convert to lowercase @@ -134,10 +180,12 @@ export const authOptions: NextAuthOptions = { token.role = mapToApplicationRoles(finalRoles); console.log("Mapped application roles:", token.role); } else if (user && user.role) { + console.log("Using direct user.role:", user.role); token.role = Array.isArray(user.role) ? user.role : [user.role]; console.log("Using user.role directly:", token.role); } else { // Default roles if no profile data available + console.log("No role data found in user object or profile"); token.role = ['user']; console.log("Using default 'user' role only"); } @@ -155,6 +203,7 @@ export const authOptions: NextAuthOptions = { console.log("Adding default 'user' role to existing token"); } + console.log("FINAL TOKEN:", JSON.stringify(token, null, 2)); return token; }, async session({ session, token }: any) { @@ -204,36 +253,82 @@ export const authOptions: NextAuthOptions = { * Maps Keycloak roles to application-specific roles */ function mapToApplicationRoles(keycloakRoles: string[]): string[] { + // Debug input + console.log("Mapping input roles:", keycloakRoles); + + // For development/testing, directly assign application roles based on username + // This helps in case Keycloak isn't properly configured + let appRoles: string[] = ['user']; // Always include 'user' role + + // The mappings object maps Keycloak role names to application role names const mappings: Record = { // Map Keycloak roles to your application's role names - 'admin': ['admin', 'dataintelligence', 'coding', 'expression', 'mediation'], - 'owner': ['admin', 'dataintelligence', 'coding', 'expression', 'mediation'], - 'cercle-admin': ['admin', 'dataintelligence', 'coding', 'expression', 'mediation'], - 'manager': ['dataintelligence', 'coding', 'expression', 'mediation'], + 'admin': ['admin', 'dataintelligence', 'coding', 'expression', 'mediation', 'entrepreneurship'], + 'owner': ['admin', 'dataintelligence', 'coding', 'expression', 'mediation', 'entrepreneurship'], + 'manager': ['dataintelligence', 'coding', 'expression', 'mediation', 'entrepreneurship'], 'developer': ['coding', 'dataintelligence'], 'data-scientist': ['dataintelligence'], 'designer': ['expression'], - 'writer': ['expression'], + 'writer': ['expression'], 'mediator': ['mediation'], + 'entrepreneur': ['entrepreneurship'], + + // Common prefixed variants + 'role_admin': ['admin', 'dataintelligence', 'coding', 'expression', 'mediation', 'entrepreneurship'], + 'realm_admin': ['admin', 'dataintelligence', 'coding', 'expression', 'mediation', 'entrepreneurship'], + 'app_admin': ['admin', 'dataintelligence', 'coding', 'expression', 'mediation', 'entrepreneurship'], + // Default access roles from Keycloak 'default-roles-cercle': ['user'], 'uma_authorization': ['user'], 'offline_access': ['user'], - // Add more mappings as needed + + // Direct mapping for flexibility + 'expression': ['expression'], + 'mediation': ['mediation'], + 'coding': ['coding'], + 'dataintelligence': ['dataintelligence'], + 'entrepreneurship': ['entrepreneurship'], }; - - // Map each role and flatten the result - let appRoles: string[] = ['user']; // Always include 'user' role + // Try to match each role with our mappings for (const role of keycloakRoles) { - const mappedRoles = mappings[role.toLowerCase()]; - if (mappedRoles) { - appRoles = [...appRoles, ...mappedRoles]; + // Try different variations of the role name + const normalizedRole = role.toLowerCase() + .replace(/^role_/i, '') // Remove ROLE_ prefix + .replace(/^realm_/i, '') // Remove REALM_ prefix + .replace(/^app_/i, ''); // Remove APP_ prefix + + console.log(`Processing role: ${role} -> normalized: ${normalizedRole}`); + + // Check for direct match + if (mappings[normalizedRole]) { + appRoles = [...appRoles, ...mappings[normalizedRole]]; + console.log(`Mapped ${role} to: ${mappings[normalizedRole].join(', ')}`); + } + // Check for partial matches + else { + for (const [mapKey, mapRoles] of Object.entries(mappings)) { + if (normalizedRole.includes(mapKey)) { + appRoles = [...appRoles, ...mapRoles]; + console.log(`Partially matched ${role} with ${mapKey} to: ${mapRoles.join(', ')}`); + } + } } } + // DEVELOPMENT OVERRIDE - Comment out in production + // Assign all roles for testing + if (process.env.NODE_ENV === 'development') { + // Add specific development testing roles + appRoles = ['user', 'admin', 'expression', 'mediation', 'coding', 'dataintelligence', 'entrepreneurship']; + console.log("DEVELOPMENT MODE: Using all roles for testing"); + } + // Remove duplicates and return - return [...new Set(appRoles)]; + const uniqueRoles = [...new Set(appRoles)]; + console.log("Final mapped roles:", uniqueRoles); + return uniqueRoles; } const handler = NextAuth(authOptions);