From d99e10067d2b6a5b72dc4da6aa3fccf2bcfae339 Mon Sep 17 00:00:00 2001 From: alma Date: Sat, 3 May 2025 16:01:12 +0200 Subject: [PATCH] equipes keycloak flow --- app/api/users/[userId]/roles/route.ts | 173 ++++++++------------------ components/sidebar.tsx | 168 ++++++------------------- 2 files changed, 87 insertions(+), 254 deletions(-) diff --git a/app/api/users/[userId]/roles/route.ts b/app/api/users/[userId]/roles/route.ts index aec8867c..8a0b56b0 100644 --- a/app/api/users/[userId]/roles/route.ts +++ b/app/api/users/[userId]/roles/route.ts @@ -3,9 +3,12 @@ import { getServerSession } from "next-auth"; import { authOptions } from "@/app/api/auth/[...nextauth]/route"; import { getKeycloakAdminClient } from "@/lib/keycloak"; +// Fix for Next.js "params should be awaited" error +export const dynamic = 'force-dynamic'; + export async function GET( request: Request, - { params }: { params: { userId?: string } } + { params }: { params: { userId: string } } ) { try { const session = await getServerSession(authOptions); @@ -13,58 +16,21 @@ export async function GET( return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } - // Just get the primitive value directly - const rawUserId = params?.userId; - const userId = typeof rawUserId === 'string' ? rawUserId : ''; + const { userId } = params; + const kcAdminClient = await getKeycloakAdminClient(); + + // Get all available roles + const availableRoles = await kcAdminClient.roles.find(); - if (!userId) { - return NextResponse.json({ error: "User ID is required" }, { status: 400 }); - } + // Get user's current roles + const userRoles = await kcAdminClient.users.listRoleMappings({ + id: userId, + }); - try { - // Check for required environment variables before attempting to connect - const missingVars = []; - if (!process.env.KEYCLOAK_BASE_URL && !process.env.KEYCLOAK_ISSUER && !process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER) { - missingVars.push('KEYCLOAK_BASE_URL or KEYCLOAK_ISSUER'); - } - if (!process.env.KEYCLOAK_ADMIN_CLIENT_ID) missingVars.push('KEYCLOAK_ADMIN_CLIENT_ID'); - if (!process.env.KEYCLOAK_ADMIN_USERNAME) missingVars.push('KEYCLOAK_ADMIN_USERNAME'); - if (!process.env.KEYCLOAK_ADMIN_PASSWORD) missingVars.push('KEYCLOAK_ADMIN_PASSWORD'); - if (!process.env.KEYCLOAK_REALM) missingVars.push('KEYCLOAK_REALM'); - - if (missingVars.length > 0) { - console.error(`Missing Keycloak environment variables: ${missingVars.join(', ')}`); - return NextResponse.json( - { - error: "Keycloak configuration incomplete", - message: "Role management is currently unavailable due to missing configuration.", - details: `Missing: ${missingVars.join(', ')}` - }, - { status: 503 } - ); - } - - const kcAdminClient = await getKeycloakAdminClient(); - - // Get all available roles - const availableRoles = await kcAdminClient.roles.find(); - - // Get user's current roles - const userRoles = await kcAdminClient.users.listRoleMappings({ - id: userId, - }); - - return NextResponse.json({ - availableRoles, - userRoles, - }); - } catch (keycloakError) { - console.error("Error connecting to Keycloak:", keycloakError); - return NextResponse.json( - { error: "Failed to connect to Keycloak service", details: String(keycloakError) }, - { status: 503 } - ); - } + return NextResponse.json({ + availableRoles, + userRoles, + }); } catch (error) { console.error("Error fetching roles:", error); return NextResponse.json( @@ -76,7 +42,7 @@ export async function GET( export async function PUT( request: Request, - { params }: { params: { userId?: string } } + { params }: { params: { userId: string } } ) { try { const session = await getServerSession(authOptions); @@ -84,83 +50,46 @@ export async function PUT( return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } - // Just get the primitive value directly - const rawUserId = params?.userId; - const userId = typeof rawUserId === 'string' ? rawUserId : ''; + const { userId } = params; + const { roles } = await request.json(); + const kcAdminClient = await getKeycloakAdminClient(); + + // Get all available roles + const availableRoles = await kcAdminClient.roles.find(); - if (!userId) { - return NextResponse.json({ error: "User ID is required" }, { status: 400 }); - } - - try { - // Check for required environment variables before attempting to connect - const missingVars = []; - if (!process.env.KEYCLOAK_BASE_URL && !process.env.KEYCLOAK_ISSUER && !process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER) { - missingVars.push('KEYCLOAK_BASE_URL or KEYCLOAK_ISSUER'); - } - if (!process.env.KEYCLOAK_ADMIN_CLIENT_ID) missingVars.push('KEYCLOAK_ADMIN_CLIENT_ID'); - if (!process.env.KEYCLOAK_ADMIN_USERNAME) missingVars.push('KEYCLOAK_ADMIN_USERNAME'); - if (!process.env.KEYCLOAK_ADMIN_PASSWORD) missingVars.push('KEYCLOAK_ADMIN_PASSWORD'); - if (!process.env.KEYCLOAK_REALM) missingVars.push('KEYCLOAK_REALM'); - - if (missingVars.length > 0) { - console.error(`Missing Keycloak environment variables: ${missingVars.join(', ')}`); - return NextResponse.json( - { - error: "Keycloak configuration incomplete", - message: "Role management is currently unavailable due to missing configuration.", - details: `Missing: ${missingVars.join(', ')}` - }, - { status: 503 } - ); - } - - const { roles } = await request.json(); - const kcAdminClient = await getKeycloakAdminClient(); + // Get current user roles + const currentRoles = await kcAdminClient.users.listRoleMappings({ + id: userId, + }); - // Get all available roles - const availableRoles = await kcAdminClient.roles.find(); - - // Get current user roles - const currentRoles = await kcAdminClient.users.listRoleMappings({ - id: userId, - }); + // Find roles to add and remove + const rolesToAdd = roles.filter( + (role: string) => !currentRoles.realmMappings?.some((r: any) => r.name === role) + ); + const rolesToRemove = currentRoles.realmMappings?.filter( + (role: any) => !roles.includes(role.name) + ); - // Find roles to add and remove - const rolesToAdd = roles.filter( - (role: string) => !currentRoles.realmMappings?.some((r: any) => r.name === role) - ); - const rolesToRemove = currentRoles.realmMappings?.filter( - (role: any) => !roles.includes(role.name) - ); - - // Add new roles - for (const roleName of rolesToAdd) { - const role = availableRoles.find((r: any) => r.name === roleName); - if (role) { - await kcAdminClient.users.addRealmRoleMappings({ - id: userId, - roles: [role as any], - }); - } - } - - // Remove old roles - if (rolesToRemove && rolesToRemove.length > 0) { - await kcAdminClient.users.delRealmRoleMappings({ + // Add new roles + for (const roleName of rolesToAdd) { + const role = availableRoles.find((r: any) => r.name === roleName); + if (role) { + await kcAdminClient.users.addRealmRoleMappings({ id: userId, - roles: rolesToRemove as any, + roles: [role as any], }); } - - return NextResponse.json({ success: true }); - } catch (keycloakError) { - console.error("Error connecting to Keycloak:", keycloakError); - return NextResponse.json( - { error: "Failed to connect to Keycloak service", details: String(keycloakError) }, - { status: 503 } - ); } + + // Remove old roles + if (rolesToRemove && rolesToRemove.length > 0) { + await kcAdminClient.users.delRealmRoleMappings({ + id: userId, + roles: rolesToRemove as any, + }); + } + + return NextResponse.json({ success: true }); } catch (error) { console.error("Error updating roles:", error); return NextResponse.json( diff --git a/components/sidebar.tsx b/components/sidebar.tsx index 2e20b026..8f2dd26d 100644 --- a/components/sidebar.tsx +++ b/components/sidebar.tsx @@ -71,66 +71,46 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) { // Function to check if user has a specific role const hasRole = (requiredRole: string | string[] | undefined) => { - // If no role is required, allow access - if (!requiredRole) { - return true; - } - - // If no session or user roles, deny access - if (!session?.user?.role) { - console.log('No user roles found in session'); + if (!requiredRole || !session?.user?.role) { + console.log('No required role or user roles found', { + requiredRole, + userRoles: session?.user?.role + }); return false; } - // Get user roles and normalize them properly const userRoles = Array.isArray(session.user.role) ? session.user.role : [session.user.role]; + const cleanUserRoles = userRoles.map(role => role.toLowerCase()); - // Filter out technical/system roles that shouldn't count for permissions - const ignoredRoles = ['offline_access', 'uma_authorization', 'default-roles-cercle']; + console.log('Debug roles:', { + rawUserRoles: session.user.role, + processedUserRoles: cleanUserRoles, + requiredRole, + pathname + }); - const cleanUserRoles = userRoles - .filter(Boolean) // Remove any null/undefined values - .filter(role => !ignoredRoles.includes(String(role))) // Filter out system roles - .map(role => { - if (typeof role !== 'string') return ''; - return role - .replace(/^\//, '') // Remove leading slash - .replace(/^ROLE_/i, '') // Remove ROLE_ prefix, case insensitive - .replace(/^default-roles-[^/]*\//i, '') // Remove realm prefix like default-roles-cercle/ - .toLowerCase(); - }) - .filter(role => role !== ''); // Remove empty strings - - // For debugging only - if (process.env.NODE_ENV === 'development') { - console.log(`Role check for: ${JSON.stringify(requiredRole)}`, { - userRoles, - ignoredRoles, - cleanUserRoles, - }); - } - - // Check against array of required roles + // If requiredRole is an array, check if user has any of the roles if (Array.isArray(requiredRole)) { - const cleanRequiredRoles = requiredRole - .filter(Boolean) - .map(role => typeof role === 'string' ? role.toLowerCase() : '') - .filter(role => role !== ''); - - const hasRequiredRole = cleanRequiredRoles.some(role => cleanUserRoles.includes(role)); - console.log(`Array role check: Required ${JSON.stringify(cleanRequiredRoles)}, Has any: ${hasRequiredRole}`); - return hasRequiredRole; + const cleanRequiredRoles = requiredRole.map(role => role.toLowerCase()); + console.log('Checking multiple roles:', { + requiredRoles: requiredRole, + cleanRequiredRoles, + userRoles: cleanUserRoles, + hasAnyRole: cleanRequiredRoles.some(role => cleanUserRoles.includes(role)), + matchingRoles: cleanRequiredRoles.filter(role => cleanUserRoles.includes(role)) + }); + return cleanRequiredRoles.some(role => cleanUserRoles.includes(role)); } - // Check against single required role - if (typeof requiredRole === 'string') { - const cleanRequiredRole = requiredRole.toLowerCase(); - const hasRequiredRole = cleanUserRoles.includes(cleanRequiredRole); - console.log(`Single role check: Required "${cleanRequiredRole}", Has: ${hasRequiredRole}`); - return hasRequiredRole; - } - - return false; + // For single role requirement + const cleanRequiredRole = requiredRole.toLowerCase(); + console.log('Checking single role:', { + requiredRole, + cleanRequiredRole, + userRoles: cleanUserRoles, + hasRole: cleanUserRoles.includes(cleanRequiredRole) + }); + return cleanUserRoles.includes(cleanRequiredRole); }; // Base menu items (available for everyone) @@ -191,39 +171,35 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) { icon: Palette, href: "/design", iframe: process.env.NEXT_PUBLIC_IFRAME_ARTLAB_URL, - requiredRole: "expression", + requiredRole: "Expression", }, { title: "Gite", icon: GitFork, href: "/gite", iframe: process.env.NEXT_PUBLIC_IFRAME_GITE_URL, - requiredRole: ["coding", "dataintelligence"], + requiredRole: ["Coding", "DataIntelligence"], }, { title: "Calcul", icon: Calculator, href: "/calcul", iframe: process.env.NEXT_PUBLIC_IFRAME_CALCULATION_URL, - requiredRole: "dataintelligence", + requiredRole: "DataIntelligence", }, { title: "Médiation", icon: Building2, href: "/mediation", iframe: process.env.NEXT_PUBLIC_IFRAME_MEDIATIONS_URL, - requiredRole: ["mediation", "expression"], + requiredRole: ["Mediation", "Expression"], }, ]; // Combine base items with role-specific items based on user roles const visibleMenuItems = [ ...baseMenuItems, - ...roleSpecificItems.filter(item => { - const isVisible = hasRole(item.requiredRole); - console.log(`Item ${item.title} with requiredRole ${JSON.stringify(item.requiredRole)} is ${isVisible ? 'visible' : 'hidden'}`); - return isVisible; - }) + ...roleSpecificItems.filter(item => hasRole(item.requiredRole)) ]; const handleNavigation = (href: string, external?: boolean) => { @@ -288,78 +264,6 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) { {item.title} ))} - - {/* Debug display only in development */} - {process.env.NODE_ENV === 'development' && ( -
-

Debug Info:

-
-

User: {session?.user?.name}

-

Email: {session?.user?.email}

-
- User Roles -
-                      {JSON.stringify(session?.user?.role, null, 2)}
-                    
-
-
- Normalized Roles -
-                      {JSON.stringify(
-                        Array.isArray(session?.user?.role) 
-                          ? session.user.role
-                              .filter(role => typeof role === 'string')
-                              .filter(role => !['offline_access', 'uma_authorization', 'default-roles-cercle'].includes(role))
-                              .map(role => 
-                                role
-                                  .replace(/^\//, '')
-                                  .replace(/^ROLE_/i, '')
-                                  .replace(/^default-roles-[^/]*\//i, '')
-                                  .toLowerCase()
-                              )
-                          : []
-                        , null, 2)}
-                    
-
-
- Role-Based Items -
- {roleSpecificItems.map(item => { - const isVisible = hasRole(item.requiredRole); - return ( -
-

Item: {item.title}

-

Required Role: {JSON.stringify(item.requiredRole)}

-

Visible: {isVisible ? '✅' : '❌'}

-
- ); - })} -
-
-
- Visible Menu Items -
-

Base Items:

-
    - {baseMenuItems.map(item => ( -
  • {item.title}
  • - ))} -
-

Role-Specific Items (After Filtering):

-
    - {roleSpecificItems - .filter(item => hasRole(item.requiredRole)) - .map(item => ( -
  • {item.title}
  • - )) - } -
-

Total Visible Items: {visibleMenuItems.length}

-
-
-
-
- )}