cleaning hard 2

This commit is contained in:
alma 2025-05-03 13:37:14 +02:00
parent 6569858fbb
commit d2f131a142
5 changed files with 72 additions and 228 deletions

View File

@ -60,32 +60,7 @@ export const authOptions: NextAuthOptions = {
clientSecret: process.env.KEYCLOAK_CLIENT_SECRET || "", clientSecret: process.env.KEYCLOAK_CLIENT_SECRET || "",
issuer: process.env.KEYCLOAK_ISSUER || "", issuer: process.env.KEYCLOAK_ISSUER || "",
profile(profile: any) { profile(profile: any) {
console.log("Raw Keycloak profile:", JSON.stringify(profile, null, 2)); console.log("Raw Keycloak profile:", profile);
// 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 // Just return a simple profile with required fields
return { return {
@ -96,7 +71,7 @@ export const authOptions: NextAuthOptions = {
username: profile.preferred_username || profile.email?.split('@')[0] || '', username: profile.preferred_username || profile.email?.split('@')[0] || '',
first_name: profile.given_name || '', first_name: profile.given_name || '',
last_name: profile.family_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 // Store raw profile data for later processing
raw_profile: profile raw_profile: profile
}; };
@ -111,16 +86,12 @@ export const authOptions: NextAuthOptions = {
async jwt({ token, account, profile, user }: any) { async jwt({ token, account, profile, user }: any) {
// Initial sign in // Initial sign in
if (account && account.access_token) { 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.accessToken = account.access_token;
token.refreshToken = account.refresh_token; token.refreshToken = account.refresh_token;
// Process the raw profile data if available // Process the raw profile data if available
if (user && user.raw_profile) { if (user && user.raw_profile) {
const rawProfile = user.raw_profile; const rawProfile = user.raw_profile;
console.log("RAW KEYCLOAK PROFILE:", JSON.stringify(rawProfile, null, 2));
// Extract roles from all possible sources // Extract roles from all possible sources
let roles: string[] = []; let roles: string[] = [];
@ -134,9 +105,6 @@ export const authOptions: NextAuthOptions = {
// Get roles from resource_access // Get roles from resource_access
if (rawProfile.resource_access) { if (rawProfile.resource_access) {
const clientId = process.env.KEYCLOAK_CLIENT_ID; 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 && if (clientId &&
rawProfile.resource_access[clientId] && rawProfile.resource_access[clientId] &&
Array.isArray(rawProfile.resource_access[clientId].roles)) { Array.isArray(rawProfile.resource_access[clientId].roles)) {
@ -150,20 +118,6 @@ export const authOptions: NextAuthOptions = {
roles = roles.concat(rawProfile.resource_access.account.roles); roles = roles.concat(rawProfile.resource_access.account.roles);
console.log("Roles from resource_access.account:", 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 // Clean up roles and convert to lowercase
@ -172,24 +126,18 @@ export const authOptions: NextAuthOptions = {
.map(role => role.toLowerCase()); .map(role => role.toLowerCase());
console.log("Cleaned raw Keycloak roles:", cleanedRoles); console.log("Cleaned raw Keycloak roles:", cleanedRoles);
console.log("Directly from cleaning process:", cleanedRoles);
// Always ensure user has basic user role // Always ensure user has basic user role
const finalRoles = [...new Set([...cleanedRoles, 'user'])]; const finalRoles = [...new Set([...cleanedRoles, 'user'])];
console.log("Input to mapping function:", finalRoles);
// Map Keycloak roles to application roles // Map Keycloak roles to application roles
token.role = mapToApplicationRoles(finalRoles); token.role = mapToApplicationRoles(finalRoles);
console.log("Mapped application roles:", token.role); console.log("Mapped application roles:", token.role);
console.log("Output from mapping function:", token.role);
} else if (user && user.role) { } else if (user && user.role) {
console.log("Using direct user.role:", user.role);
token.role = Array.isArray(user.role) ? user.role : [user.role]; token.role = Array.isArray(user.role) ? user.role : [user.role];
console.log("Using user.role directly:", token.role); console.log("Using user.role directly:", token.role);
} else { } else {
// Default roles if no profile data available // Default roles if no profile data available
console.log("No role data found in user object or profile");
token.role = ['user']; token.role = ['user'];
console.log("Using default 'user' role only"); 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("Adding default 'user' role to existing token");
} }
console.log("FINAL TOKEN:", JSON.stringify(token, null, 2));
return token; return token;
}, },
async session({ session, token }: any) { async session({ session, token }: any) {
@ -257,80 +204,36 @@ export const authOptions: NextAuthOptions = {
* Maps Keycloak roles to application-specific roles * Maps Keycloak roles to application-specific roles
*/ */
function mapToApplicationRoles(keycloakRoles: string[]): string[] { 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<string, string[]> = { const mappings: Record<string, string[]> = {
// Map Keycloak roles to your application's role names // Map Keycloak roles to your application's role names
'admin': ['admin', 'dataintelligence', 'coding', 'expression', 'mediation', 'entrepreneurship'], 'admin': ['admin', 'dataintelligence', 'coding', 'expression', 'mediation'],
'owner': ['admin', 'dataintelligence', 'coding', 'expression', 'mediation', 'entrepreneurship'], 'owner': ['admin', 'dataintelligence', 'coding', 'expression', 'mediation'],
'manager': ['dataintelligence', 'coding', 'expression', 'mediation', 'entrepreneurship'], 'cercle-admin': ['admin', 'dataintelligence', 'coding', 'expression', 'mediation'],
'manager': ['dataintelligence', 'coding', 'expression', 'mediation'],
'developer': ['coding', 'dataintelligence'], 'developer': ['coding', 'dataintelligence'],
'data-scientist': ['dataintelligence'], 'data-scientist': ['dataintelligence'],
'designer': ['expression'], 'designer': ['expression'],
'writer': ['expression'], 'writer': ['expression'],
'mediator': ['mediation'], '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 access roles from Keycloak
'default-roles-cercle': ['user'], 'default-roles-cercle': ['user'],
'uma_authorization': ['user'], 'uma_authorization': ['user'],
'offline_access': ['user'], 'offline_access': ['user'],
// Add more mappings as needed
// Direct mapping for flexibility
'expression': ['expression'],
'mediation': ['mediation'],
'coding': ['coding'],
'dataintelligence': ['dataintelligence'],
'entrepreneurship': ['entrepreneurship'],
}; };
// Try to match each role with our mappings // Map each role and flatten the result
let appRoles: string[] = ['user']; // Always include 'user' role
for (const role of keycloakRoles) { for (const role of keycloakRoles) {
// Try different variations of the role name const mappedRoles = mappings[role.toLowerCase()];
const normalizedRole = role.toLowerCase() if (mappedRoles) {
.replace(/^role_/i, '') // Remove ROLE_ prefix appRoles = [...appRoles, ...mappedRoles];
.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(', ')}`);
}
}
} }
} }
// Remove duplicates and return // Remove duplicates and return
const uniqueRoles = [...new Set(appRoles)]; return [...new Set(appRoles)];
console.log("Final mapped roles:", uniqueRoles);
return uniqueRoles;
} }
const handler = NextAuth(authOptions); const handler = NextAuth(authOptions);

View File

@ -5,8 +5,6 @@ import { usePathname, useRouter } from "next/navigation";
import { useEffect } from "react"; import { useEffect } from "react";
export function AuthCheck({ children }: { children: React.ReactNode }) { export function AuthCheck({ children }: { children: React.ReactNode }) {
// Wrap in try-catch to ensure it never crashes
try {
const session = useSession(); const session = useSession();
const pathname = usePathname(); const pathname = usePathname();
const router = useRouter(); const router = useRouter();
@ -15,15 +13,9 @@ export function AuthCheck({ children }: { children: React.ReactNode }) {
const status = session?.status || "loading"; const status = session?.status || "loading";
useEffect(() => { useEffect(() => {
try {
// Only redirect if we're certain the user is unauthenticated // Only redirect if we're certain the user is unauthenticated
if (status === "unauthenticated" && pathname && !pathname.includes("/signin")) { if (status === "unauthenticated" && !pathname?.includes("/signin")) {
if (router && typeof router.push === 'function') { router?.push("/signin");
router.push("/signin");
}
}
} catch (error) {
console.error("Error in AuthCheck useEffect:", error);
} }
}, [status, router, pathname]); }, [status, router, pathname]);
@ -37,15 +29,10 @@ export function AuthCheck({ children }: { children: React.ReactNode }) {
} }
// Don't render on unauthenticated // Don't render on unauthenticated
if (status === "unauthenticated" && pathname && !pathname.includes("/signin")) { if (status === "unauthenticated" && !pathname?.includes("/signin")) {
return null; return null;
} }
// Render children if authenticated // Render children if authenticated
return <>{children}</>; return <>{children}</>;
} catch (error) {
// If anything fails, just render the children
console.error("Error in AuthCheck component:", error);
return <>{children}</>;
}
} }

View File

@ -46,22 +46,13 @@ const backgroundImages = [
]; ];
export function useBackgroundImage() { export function useBackgroundImage() {
// Safety checks // Initialize with a safe default in case the array is empty
if (!Array.isArray(backgroundImages) || backgroundImages.length === 0) { const defaultBackground = backgroundImages.length > 0 ? backgroundImages[0] : '';
return {
currentBackground: '',
changeBackground: () => {}
};
}
// Initialize with a safe default
const defaultBackground = backgroundImages[0] || '';
const [currentBackground, setCurrentBackground] = useState(defaultBackground); const [currentBackground, setCurrentBackground] = useState(defaultBackground);
const changeBackground = () => { const changeBackground = () => {
try { // Safety check to prevent issues if the array is empty
// Safety check to prevent issues if (backgroundImages.length === 0) return;
if (!Array.isArray(backgroundImages) || backgroundImages.length === 0) return;
const currentIndex = backgroundImages.indexOf(currentBackground); const currentIndex = backgroundImages.indexOf(currentBackground);
// Handle case where current background is not in the array // Handle case where current background is not in the array
@ -69,27 +60,17 @@ export function useBackgroundImage() {
(currentIndex + 1) % backgroundImages.length : (currentIndex + 1) % backgroundImages.length :
0; 0;
setCurrentBackground(backgroundImages[nextIndex]); setCurrentBackground(backgroundImages[nextIndex]);
} catch (error) {
console.error("Error changing background:", error);
}
}; };
useEffect(() => { useEffect(() => {
try { // Set initial random background
// Set initial random background safely if (backgroundImages.length > 0) {
if (Array.isArray(backgroundImages) && backgroundImages.length > 0) {
const randomIndex = Math.floor(Math.random() * backgroundImages.length); const randomIndex = Math.floor(Math.random() * backgroundImages.length);
setCurrentBackground(backgroundImages[randomIndex] || ''); setCurrentBackground(backgroundImages[randomIndex]);
}
} catch (error) {
console.error("Error setting initial background in useEffect:", error);
} }
}, []); }, []);
return { return { currentBackground, changeBackground };
currentBackground: currentBackground || '',
changeBackground
};
} }
export function BackgroundSwitcher({ children }: { children: React.ReactNode }) { export function BackgroundSwitcher({ children }: { children: React.ReactNode }) {

View File

@ -13,37 +13,16 @@ interface LayoutWrapperProps {
} }
export function LayoutWrapper({ children, isSignInPage, isAuthenticated }: LayoutWrapperProps) { export function LayoutWrapper({ children, isSignInPage, isAuthenticated }: LayoutWrapperProps) {
// Default safe values // Add try-catch to handle potential errors in the hook
let currentBackground = ''; let backgroundData = { currentBackground: '', changeBackground: () => {} };
let changeBackground = () => {};
// Try to use the background hook if available
try { try {
if (typeof useBackgroundImage === 'function') { backgroundData = useBackgroundImage();
const backgroundData = useBackgroundImage();
if (backgroundData && typeof backgroundData === 'object') {
currentBackground = backgroundData.currentBackground || '';
changeBackground = typeof backgroundData.changeBackground === 'function'
? backgroundData.changeBackground
: () => {};
}
}
} catch (error) { } catch (error) {
console.error("Failed to initialize background:", error); console.error("Error initializing background:", error);
} }
// Safely wrap the click handler to prevent errors const { currentBackground, changeBackground } = backgroundData;
const handleBackgroundClick = (e: React.MouseEvent) => {
try {
if (!isSignInPage && typeof changeBackground === 'function') {
changeBackground();
}
} catch (error) {
console.error("Error in background click handler:", error);
}
};
// Safely render
return ( return (
<AuthCheck> <AuthCheck>
{!isSignInPage && isAuthenticated && <MainNav />} {!isSignInPage && isAuthenticated && <MainNav />}
@ -60,7 +39,7 @@ export function LayoutWrapper({ children, isSignInPage, isAuthenticated }: Layou
transition: 'background-image 0.5s ease-in-out' transition: 'background-image 0.5s ease-in-out'
} : {} } : {}
} }
onClick={handleBackgroundClick} onClick={!isSignInPage && typeof changeBackground === 'function' ? changeBackground : undefined}
> >
<main>{children}</main> <main>{children}</main>
</div> </div>

View File

@ -9,8 +9,7 @@ interface ProvidersProps {
} }
export default function Providers({ children, session }: ProvidersProps) { export default function Providers({ children, session }: ProvidersProps) {
try { // Ensure session is properly handled - if it's undefined, provide an empty object
// Ensure session is properly handled - if it's undefined, provide null
const safeSession = session === undefined ? null : session; const safeSession = session === undefined ? null : session;
return ( return (
@ -20,9 +19,4 @@ export default function Providers({ children, session }: ProvidersProps) {
</SessionProvider> </SessionProvider>
</ThemeProvider> </ThemeProvider>
); );
} catch (error) {
// If anything goes catastrophically wrong, at least render the children
console.error("Error in Providers component:", error);
return <>{children}</>;
}
} }