diff --git a/app/api/groups/[groupId]/members/route.ts b/app/api/groups/[groupId]/members/route.ts new file mode 100644 index 00000000..64fcf5fc --- /dev/null +++ b/app/api/groups/[groupId]/members/route.ts @@ -0,0 +1,121 @@ +import { getServerSession } from "next-auth/next"; +import { authOptions } from "@/app/api/auth/[...nextauth]/route"; +import { NextResponse } from "next/server"; + +export async function GET( + req: Request, + { params }: { params: { groupId: string } } +) { + const session = await getServerSession(authOptions); + + if (!session) { + return NextResponse.json({ error: "Non autorisé" }, { status: 401 }); + } + + try { + // Get client credentials token + const tokenResponse = await fetch( + `${process.env.KEYCLOAK_BASE_URL}/realms/${process.env.KEYCLOAK_REALM}/protocol/openid-connect/token`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: new URLSearchParams({ + grant_type: 'client_credentials', + client_id: process.env.KEYCLOAK_CLIENT_ID!, + client_secret: process.env.KEYCLOAK_CLIENT_SECRET!, + }), + } + ); + + const tokenData = await tokenResponse.json(); + + if (!tokenResponse.ok) { + console.error("Failed to get token:", tokenData); + return NextResponse.json({ error: "Failed to get token" }, { status: 500 }); + } + + // Get group members + const membersResponse = await fetch( + `${process.env.KEYCLOAK_BASE_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/groups/${params.groupId}/members`, + { + headers: { + 'Authorization': `Bearer ${tokenData.access_token}`, + }, + } + ); + + if (!membersResponse.ok) { + const errorData = await membersResponse.json(); + console.error("Failed to get group members:", errorData); + return NextResponse.json({ error: "Failed to get group members" }, { status: membersResponse.status }); + } + + const members = await membersResponse.json(); + return NextResponse.json(members); + } catch (error) { + console.error("Error in get group members:", error); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); + } +} + +export async function POST( + req: Request, + { params }: { params: { groupId: string } } +) { + const session = await getServerSession(authOptions); + + if (!session) { + return NextResponse.json({ error: "Non autorisé" }, { status: 401 }); + } + + try { + const { userId } = await req.json(); + + // Get client credentials token + const tokenResponse = await fetch( + `${process.env.KEYCLOAK_BASE_URL}/realms/${process.env.KEYCLOAK_REALM}/protocol/openid-connect/token`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: new URLSearchParams({ + grant_type: 'client_credentials', + client_id: process.env.KEYCLOAK_CLIENT_ID!, + client_secret: process.env.KEYCLOAK_CLIENT_SECRET!, + }), + } + ); + + const tokenData = await tokenResponse.json(); + + if (!tokenResponse.ok) { + console.error("Failed to get token:", tokenData); + return NextResponse.json({ error: "Failed to get token" }, { status: 500 }); + } + + // Add user to group + const addResponse = await fetch( + `${process.env.KEYCLOAK_BASE_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/users/${userId}/groups/${params.groupId}`, + { + method: 'PUT', + headers: { + 'Authorization': `Bearer ${tokenData.access_token}`, + }, + } + ); + + if (!addResponse.ok) { + const errorData = await addResponse.json(); + console.error("Failed to add user to group:", errorData); + return NextResponse.json({ error: "Failed to add user to group" }, { status: addResponse.status }); + } + + return NextResponse.json({ success: true }); + } catch (error) { + console.error("Error in add user to group:", error); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); + } +} \ No newline at end of file diff --git a/app/api/users/[userId]/roles/route.ts b/app/api/users/[userId]/roles/route.ts index 02119000..a52f1474 100644 --- a/app/api/users/[userId]/roles/route.ts +++ b/app/api/users/[userId]/roles/route.ts @@ -1,149 +1,97 @@ -import { getServerSession } from "next-auth/next"; -import { authOptions } from "@/app/api/auth/[...nextauth]/route"; import { NextResponse } from "next/server"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/app/api/auth/[...nextauth]/route"; +import { getKeycloakAdminClient } from "@/lib/keycloak"; +import { RoleRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation"; -async function getAdminToken() { +export async function GET( + request: Request, + { params }: { params: { userId: string } } +) { try { - const tokenResponse = await fetch( - `${process.env.KEYCLOAK_BASE_URL}/realms/${process.env.KEYCLOAK_REALM}/protocol/openid-connect/token`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - body: new URLSearchParams({ - grant_type: 'client_credentials', - client_id: process.env.KEYCLOAK_CLIENT_ID!, - client_secret: process.env.KEYCLOAK_CLIENT_SECRET!, - }), - } - ); - - const data = await tokenResponse.json(); - - if (!tokenResponse.ok || !data.access_token) { - console.error('Token Error:', data); - return null; + const session = await getServerSession(authOptions); + if (!session) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } - return data.access_token; + const { userId } = params; + 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 (error) { - console.error('Token Error:', error); - return null; + console.error("Error fetching roles:", error); + return NextResponse.json( + { error: "Failed to fetch roles" }, + { status: 500 } + ); } } export async function PUT( - req: Request, + request: Request, { params }: { params: { userId: string } } ) { - const session = await getServerSession(authOptions); - - if (!session) { - return NextResponse.json({ error: "Non autorisé" }, { status: 401 }); - } - try { - const { roles } = await req.json(); - const token = await getAdminToken(); - - if (!token) { - return NextResponse.json({ error: "Erreur d'authentification" }, { status: 401 }); + const session = await getServerSession(authOptions); + if (!session) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } - // First, get all available roles from Keycloak - const rolesResponse = await fetch( - `${process.env.KEYCLOAK_BASE_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/roles`, - { - headers: { - Authorization: `Bearer ${token}`, - }, - } - ); + const { userId } = params; + const { roles } = await request.json(); + const kcAdminClient = await getKeycloakAdminClient(); - if (!rolesResponse.ok) { - const errorData = await rolesResponse.json(); - console.error("Failed to fetch roles:", errorData); - return NextResponse.json({ error: "Erreur lors de la récupération des rôles" }, { status: rolesResponse.status }); - } - - const availableRoles = await rolesResponse.json(); - console.log("Available roles:", availableRoles); - - // Map role names to role objects - const roleObjects = roles.map((roleName: string) => { - const role = availableRoles.find((r: any) => r.name === roleName); - if (!role) { - throw new Error(`Role ${roleName} not found in Keycloak`); - } - return role; + // Get all available roles + const availableRoles = await kcAdminClient.roles.find(); + + // Get current user roles + const currentRoles = await kcAdminClient.users.listRoleMappings({ + id: userId, }); - // First, get current role mappings - const currentMappingsResponse = await fetch( - `${process.env.KEYCLOAK_BASE_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/users/${params.userId}/role-mappings/realm`, - { - headers: { - Authorization: `Bearer ${token}`, - }, - } + // Find roles to add and remove + const rolesToAdd = roles.filter( + (role: string) => !currentRoles.clientMappings?.some((r: RoleRepresentation) => r.name === role) + ); + const rolesToRemove = currentRoles.clientMappings?.filter( + (role: RoleRepresentation) => !roles.includes(role.name) ); - if (!currentMappingsResponse.ok) { - const errorData = await currentMappingsResponse.json(); - console.error("Failed to fetch current role mappings:", errorData); - return NextResponse.json({ error: "Erreur lors de la récupération des rôles actuels" }, { status: currentMappingsResponse.status }); - } - - const currentMappings = await currentMappingsResponse.json(); - - // Remove all current role mappings - if (currentMappings.length > 0) { - const deleteResponse = await fetch( - `${process.env.KEYCLOAK_BASE_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/users/${params.userId}/role-mappings/realm`, - { - method: 'DELETE', - headers: { - Authorization: `Bearer ${token}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify(currentMappings), - } - ); - - if (!deleteResponse.ok) { - const errorData = await deleteResponse.json(); - console.error("Failed to remove current roles:", errorData); - return NextResponse.json({ error: "Erreur lors de la suppression des rôles actuels" }, { status: deleteResponse.status }); + // Add new roles + for (const roleName of rolesToAdd) { + const role = availableRoles.find((r: RoleRepresentation) => r.name === roleName); + if (role) { + await kcAdminClient.users.addRealmRoleMappings({ + id: userId, + roles: [role], + }); } } - // Add new role mappings - if (roleObjects.length > 0) { - const addResponse = await fetch( - `${process.env.KEYCLOAK_BASE_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/users/${params.userId}/role-mappings/realm`, - { - method: 'POST', - headers: { - Authorization: `Bearer ${token}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify(roleObjects), - } - ); - - if (!addResponse.ok) { - const errorData = await addResponse.json(); - console.error("Failed to add new roles:", errorData); - return NextResponse.json({ error: "Erreur lors de l'ajout des nouveaux rôles" }, { status: addResponse.status }); - } + // Remove old roles + if (rolesToRemove && rolesToRemove.length > 0) { + await kcAdminClient.users.delRealmRoleMappings({ + id: userId, + roles: rolesToRemove, + }); } - return NextResponse.json({ success: true, roles }); + return NextResponse.json({ success: true }); } catch (error) { - console.error("Error in update roles:", error); + console.error("Error updating roles:", error); return NextResponse.json( - { error: error instanceof Error ? error.message : "Une erreur est survenue" }, + { error: "Failed to update roles" }, { status: 500 } ); } diff --git a/components/users/users-table.tsx b/components/users/users-table.tsx index db6aabbc..6f0393b9 100644 --- a/components/users/users-table.tsx +++ b/components/users/users-table.tsx @@ -551,6 +551,7 @@ export function UsersTable({ userRole = [] }: UsersTableProps) { variant="ghost" className="h-8 w-8 p-0" onClick={(e) => { + e.preventDefault(); e.stopPropagation(); }} > @@ -562,6 +563,7 @@ export function UsersTable({ userRole = [] }: UsersTableProps) { Actions { + e.preventDefault(); e.stopPropagation(); handleEdit(user.id); }} @@ -571,6 +573,7 @@ export function UsersTable({ userRole = [] }: UsersTableProps) { { + e.preventDefault(); e.stopPropagation(); handleManageRoles(user.id); }} @@ -580,6 +583,7 @@ export function UsersTable({ userRole = [] }: UsersTableProps) { { + e.preventDefault(); e.stopPropagation(); handleChangePassword(user.id); }} @@ -589,6 +593,7 @@ export function UsersTable({ userRole = [] }: UsersTableProps) { { + e.preventDefault(); e.stopPropagation(); handleToggleUserStatus(user.id, user.enabled); }} @@ -609,6 +614,7 @@ export function UsersTable({ userRole = [] }: UsersTableProps) { { + e.preventDefault(); e.stopPropagation(); handleDelete(user.id); }}