diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts index 40963c54..7f846362 100644 --- a/app/api/auth/[...nextauth]/route.ts +++ b/app/api/auth/[...nextauth]/route.ts @@ -60,32 +60,7 @@ export const authOptions: NextAuthOptions = { clientSecret: process.env.KEYCLOAK_CLIENT_SECRET || "", issuer: process.env.KEYCLOAK_ISSUER || "", profile(profile: any) { - 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); + console.log("Raw Keycloak profile:", profile); // Just return a simple profile with required fields return { @@ -96,7 +71,7 @@ export const authOptions: NextAuthOptions = { username: profile.preferred_username || profile.email?.split('@')[0] || '', first_name: profile.given_name || '', last_name: profile.family_name || '', - role: roles.length > 0 ? roles : ['user'], + role: profile.realm_access?.roles || ['user'], // Store raw profile data for later processing raw_profile: profile }; @@ -111,16 +86,12 @@ 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[] = []; @@ -134,9 +105,6 @@ 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)) { @@ -150,20 +118,6 @@ 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 @@ -172,24 +126,18 @@ export const authOptions: NextAuthOptions = { .map(role => role.toLowerCase()); console.log("Cleaned raw Keycloak roles:", cleanedRoles); - console.log("Directly from cleaning process:", cleanedRoles); // Always ensure user has basic user role const finalRoles = [...new Set([...cleanedRoles, 'user'])]; - console.log("Input to mapping function:", finalRoles); - // Map Keycloak roles to application roles token.role = mapToApplicationRoles(finalRoles); console.log("Mapped application roles:", token.role); - console.log("Output from mapping function:", 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"); } @@ -207,7 +155,6 @@ 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) { @@ -257,80 +204,36 @@ 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', 'entrepreneurship'], - 'owner': ['admin', 'dataintelligence', 'coding', 'expression', 'mediation', 'entrepreneurship'], - 'manager': ['dataintelligence', 'coding', 'expression', 'mediation', 'entrepreneurship'], + 'admin': ['admin', 'dataintelligence', 'coding', 'expression', 'mediation'], + 'owner': ['admin', 'dataintelligence', 'coding', 'expression', 'mediation'], + 'cercle-admin': ['admin', 'dataintelligence', 'coding', 'expression', 'mediation'], + 'manager': ['dataintelligence', 'coding', 'expression', 'mediation'], 'developer': ['coding', 'dataintelligence'], 'data-scientist': ['dataintelligence'], 'designer': ['expression'], - 'writer': ['expression'], + 'writer': ['expression'], 'mediator': ['mediation'], - 'entrepreneur': ['entrepreneurship'], - - // Support for capitalized role names from Keycloak - 'Expression': ['expression'], - 'Mediation': ['mediation'], - 'DataIntelligence': ['dataintelligence'], - 'Admin': ['admin', 'dataintelligence', 'coding', 'expression', 'mediation', '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'], - - // Direct mapping for flexibility - 'expression': ['expression'], - 'mediation': ['mediation'], - 'coding': ['coding'], - 'dataintelligence': ['dataintelligence'], - 'entrepreneurship': ['entrepreneurship'], + // Add more mappings as needed }; + + // 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) { - // 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(', ')}`); - } - } + const mappedRoles = mappings[role.toLowerCase()]; + if (mappedRoles) { + appRoles = [...appRoles, ...mappedRoles]; } } // Remove duplicates and return - const uniqueRoles = [...new Set(appRoles)]; - console.log("Final mapped roles:", uniqueRoles); - return uniqueRoles; + return [...new Set(appRoles)]; } const handler = NextAuth(authOptions); diff --git a/components/auth/auth-check.tsx b/components/auth/auth-check.tsx index 8317ef04..d41f1053 100644 --- a/components/auth/auth-check.tsx +++ b/components/auth/auth-check.tsx @@ -5,47 +5,34 @@ import { usePathname, useRouter } from "next/navigation"; import { useEffect } from "react"; export function AuthCheck({ children }: { children: React.ReactNode }) { - // Wrap in try-catch to ensure it never crashes - try { - const session = useSession(); - const pathname = usePathname(); - const router = useRouter(); + const session = useSession(); + const pathname = usePathname(); + const router = useRouter(); - // Safely extract status with a fallback - const status = session?.status || "loading"; + // Safely extract status with a fallback + const status = session?.status || "loading"; - useEffect(() => { - try { - // Only redirect if we're certain the user is unauthenticated - if (status === "unauthenticated" && pathname && !pathname.includes("/signin")) { - if (router && typeof router.push === 'function') { - router.push("/signin"); - } - } - } catch (error) { - console.error("Error in AuthCheck useEffect:", error); - } - }, [status, router, pathname]); - - // Simple loading state - if (status === "loading") { - return ( -
-
-
- ); + useEffect(() => { + // Only redirect if we're certain the user is unauthenticated + if (status === "unauthenticated" && !pathname?.includes("/signin")) { + router?.push("/signin"); } + }, [status, router, pathname]); - // Don't render on unauthenticated - if (status === "unauthenticated" && pathname && !pathname.includes("/signin")) { - return null; - } - - // Render children if authenticated - return <>{children}; - } catch (error) { - // If anything fails, just render the children - console.error("Error in AuthCheck component:", error); - return <>{children}; + // Simple loading state + if (status === "loading") { + return ( +
+
+
+ ); } + + // Don't render on unauthenticated + if (status === "unauthenticated" && !pathname?.includes("/signin")) { + return null; + } + + // Render children if authenticated + return <>{children}; } \ No newline at end of file diff --git a/components/background-switcher.tsx b/components/background-switcher.tsx index 6adce35e..6b75f7de 100644 --- a/components/background-switcher.tsx +++ b/components/background-switcher.tsx @@ -46,50 +46,31 @@ const backgroundImages = [ ]; export function useBackgroundImage() { - // Safety checks - if (!Array.isArray(backgroundImages) || backgroundImages.length === 0) { - return { - currentBackground: '', - changeBackground: () => {} - }; - } - - // Initialize with a safe default - const defaultBackground = backgroundImages[0] || ''; + // Initialize with a safe default in case the array is empty + const defaultBackground = backgroundImages.length > 0 ? backgroundImages[0] : ''; const [currentBackground, setCurrentBackground] = useState(defaultBackground); const changeBackground = () => { - try { - // Safety check to prevent issues - if (!Array.isArray(backgroundImages) || backgroundImages.length === 0) return; - - const currentIndex = backgroundImages.indexOf(currentBackground); - // Handle case where current background is not in the array - const nextIndex = currentIndex >= 0 ? - (currentIndex + 1) % backgroundImages.length : - 0; - setCurrentBackground(backgroundImages[nextIndex]); - } catch (error) { - console.error("Error changing background:", error); - } + // Safety check to prevent issues if the array is empty + if (backgroundImages.length === 0) return; + + const currentIndex = backgroundImages.indexOf(currentBackground); + // Handle case where current background is not in the array + const nextIndex = currentIndex >= 0 ? + (currentIndex + 1) % backgroundImages.length : + 0; + setCurrentBackground(backgroundImages[nextIndex]); }; useEffect(() => { - try { - // Set initial random background safely - if (Array.isArray(backgroundImages) && backgroundImages.length > 0) { - const randomIndex = Math.floor(Math.random() * backgroundImages.length); - setCurrentBackground(backgroundImages[randomIndex] || ''); - } - } catch (error) { - console.error("Error setting initial background in useEffect:", error); + // Set initial random background + if (backgroundImages.length > 0) { + const randomIndex = Math.floor(Math.random() * backgroundImages.length); + setCurrentBackground(backgroundImages[randomIndex]); } }, []); - return { - currentBackground: currentBackground || '', - changeBackground - }; + return { currentBackground, changeBackground }; } export function BackgroundSwitcher({ children }: { children: React.ReactNode }) { diff --git a/components/layout/layout-wrapper.tsx b/components/layout/layout-wrapper.tsx index 50cd6043..daaf6f0a 100644 --- a/components/layout/layout-wrapper.tsx +++ b/components/layout/layout-wrapper.tsx @@ -13,37 +13,16 @@ interface LayoutWrapperProps { } export function LayoutWrapper({ children, isSignInPage, isAuthenticated }: LayoutWrapperProps) { - // Default safe values - let currentBackground = ''; - let changeBackground = () => {}; - - // Try to use the background hook if available + // Add try-catch to handle potential errors in the hook + let backgroundData = { currentBackground: '', changeBackground: () => {} }; try { - if (typeof useBackgroundImage === 'function') { - const backgroundData = useBackgroundImage(); - if (backgroundData && typeof backgroundData === 'object') { - currentBackground = backgroundData.currentBackground || ''; - changeBackground = typeof backgroundData.changeBackground === 'function' - ? backgroundData.changeBackground - : () => {}; - } - } + backgroundData = useBackgroundImage(); } catch (error) { - console.error("Failed to initialize background:", error); + console.error("Error initializing background:", error); } - // Safely wrap the click handler to prevent errors - const handleBackgroundClick = (e: React.MouseEvent) => { - try { - if (!isSignInPage && typeof changeBackground === 'function') { - changeBackground(); - } - } catch (error) { - console.error("Error in background click handler:", error); - } - }; + const { currentBackground, changeBackground } = backgroundData; - // Safely render return ( {!isSignInPage && isAuthenticated && } @@ -60,7 +39,7 @@ export function LayoutWrapper({ children, isSignInPage, isAuthenticated }: Layou transition: 'background-image 0.5s ease-in-out' } : {} } - onClick={handleBackgroundClick} + onClick={!isSignInPage && typeof changeBackground === 'function' ? changeBackground : undefined} >
{children}
diff --git a/components/providers.tsx b/components/providers.tsx index 10acfaf2..c30337c0 100644 --- a/components/providers.tsx +++ b/components/providers.tsx @@ -9,20 +9,14 @@ interface ProvidersProps { } export default function Providers({ children, session }: ProvidersProps) { - try { - // Ensure session is properly handled - if it's undefined, provide null - const safeSession = session === undefined ? null : session; - - return ( - - - {children} - - - ); - } catch (error) { - // If anything goes catastrophically wrong, at least render the children - console.error("Error in Providers component:", error); - return <>{children}; - } + // Ensure session is properly handled - if it's undefined, provide an empty object + const safeSession = session === undefined ? null : session; + + return ( + + + {children} + + + ); } \ No newline at end of file