equipes keycloak flow
This commit is contained in:
parent
ca3bdb0f07
commit
d99e10067d
@ -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(
|
||||
|
||||
@ -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) {
|
||||
<span>{item.title}</span>
|
||||
</Button>
|
||||
))}
|
||||
|
||||
{/* Debug display only in development */}
|
||||
{process.env.NODE_ENV === 'development' && (
|
||||
<div className="p-2 mt-4 border border-gray-200 rounded bg-blue-50 text-xs">
|
||||
<p className="font-bold">Debug Info:</p>
|
||||
<div className="mt-1">
|
||||
<p>User: {session?.user?.name}</p>
|
||||
<p>Email: {session?.user?.email}</p>
|
||||
<details>
|
||||
<summary className="cursor-pointer">User Roles</summary>
|
||||
<pre className="mt-1 p-1 bg-white text-[10px] overflow-x-auto">
|
||||
{JSON.stringify(session?.user?.role, null, 2)}
|
||||
</pre>
|
||||
</details>
|
||||
<details>
|
||||
<summary className="cursor-pointer">Normalized Roles</summary>
|
||||
<pre className="mt-1 p-1 bg-white text-[10px] overflow-x-auto">
|
||||
{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)}
|
||||
</pre>
|
||||
</details>
|
||||
<details>
|
||||
<summary className="cursor-pointer">Role-Based Items</summary>
|
||||
<div className="mt-1 p-1 bg-white text-[10px] overflow-x-auto">
|
||||
{roleSpecificItems.map(item => {
|
||||
const isVisible = hasRole(item.requiredRole);
|
||||
return (
|
||||
<div key={item.title} className="mb-1 pb-1 border-b">
|
||||
<p><strong>Item:</strong> {item.title}</p>
|
||||
<p><strong>Required Role:</strong> {JSON.stringify(item.requiredRole)}</p>
|
||||
<p><strong>Visible:</strong> {isVisible ? '✅' : '❌'}</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</details>
|
||||
<details>
|
||||
<summary className="cursor-pointer">Visible Menu Items</summary>
|
||||
<div className="mt-1 p-1 bg-white text-[10px] overflow-x-auto">
|
||||
<h3 className="font-bold">Base Items:</h3>
|
||||
<ul className="list-disc pl-3">
|
||||
{baseMenuItems.map(item => (
|
||||
<li key={item.title}>{item.title}</li>
|
||||
))}
|
||||
</ul>
|
||||
<h3 className="font-bold mt-2">Role-Specific Items (After Filtering):</h3>
|
||||
<ul className="list-disc pl-3">
|
||||
{roleSpecificItems
|
||||
.filter(item => hasRole(item.requiredRole))
|
||||
.map(item => (
|
||||
<li key={item.title}>{item.title}</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
<h3 className="font-bold mt-2">Total Visible Items: {visibleMenuItems.length}</h3>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user