diff --git a/app/api/users/[userId]/password/route.ts b/app/api/users/[userId]/password/route.ts new file mode 100644 index 00000000..31ade16c --- /dev/null +++ b/app/api/users/[userId]/password/route.ts @@ -0,0 +1,69 @@ +import { getServerSession } from "next-auth/next"; +import { authOptions } from "@/app/api/auth/[...nextauth]/route"; +import { NextResponse } from "next/server"; + +export async function PUT( + req: Request, + { params }: { params: { userId: string } } +) { + const session = await getServerSession(authOptions); + + if (!session) { + return NextResponse.json({ error: "Non autorisé" }, { status: 401 }); + } + + try { + const { password } = 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 }); + } + + // Reset password + const passwordResponse = await fetch( + `${process.env.KEYCLOAK_BASE_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/users/${params.userId}/reset-password`, + { + method: 'PUT', + headers: { + 'Authorization': `Bearer ${tokenData.access_token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + type: "password", + value: password, + temporary: false, + }), + } + ); + + if (!passwordResponse.ok) { + const errorData = await passwordResponse.json(); + console.error("Failed to reset password:", errorData); + return NextResponse.json({ error: "Failed to reset password" }, { status: passwordResponse.status }); + } + + return NextResponse.json({ success: true }); + } catch (error) { + console.error("Error in reset password:", 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 new file mode 100644 index 00000000..7b562d2c --- /dev/null +++ b/app/api/users/[userId]/roles/route.ts @@ -0,0 +1,92 @@ +import { getServerSession } from "next-auth/next"; +import { authOptions } from "@/app/api/auth/[...nextauth]/route"; +import { NextResponse } from "next/server"; + +export async function PUT( + req: 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(); + + // 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 available roles + const rolesResponse = await fetch( + `${process.env.KEYCLOAK_BASE_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/roles`, + { + headers: { + 'Authorization': `Bearer ${tokenData.access_token}`, + }, + } + ); + + if (!rolesResponse.ok) { + const errorData = await rolesResponse.json(); + console.error("Failed to get roles:", errorData); + return NextResponse.json({ error: "Failed to get roles" }, { status: rolesResponse.status }); + } + + const availableRoles = await rolesResponse.json(); + + // 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`); + } + return role; + }); + + // Update user roles + const updateResponse = await fetch( + `${process.env.KEYCLOAK_BASE_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/users/${params.userId}/role-mappings/realm`, + { + method: 'POST', + headers: { + 'Authorization': `Bearer ${tokenData.access_token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(roleObjects), + } + ); + + if (!updateResponse.ok) { + const errorData = await updateResponse.json(); + console.error("Failed to update roles:", errorData); + return NextResponse.json({ error: "Failed to update roles" }, { status: updateResponse.status }); + } + + return NextResponse.json({ success: true }); + } catch (error) { + console.error("Error in update roles:", error); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); + } +} \ No newline at end of file diff --git a/app/api/users/[userId]/route.ts b/app/api/users/[userId]/route.ts new file mode 100644 index 00000000..caf2a6db --- /dev/null +++ b/app/api/users/[userId]/route.ts @@ -0,0 +1,123 @@ +import { getServerSession } from "next-auth/next"; +import { authOptions } from "@/app/api/auth/[...nextauth]/route"; +import { NextResponse } from "next/server"; + +export async function DELETE( + req: Request, + { params }: { params: { userId: 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 }); + } + + // Delete user + const deleteResponse = await fetch( + `${process.env.KEYCLOAK_BASE_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/users/${params.userId}`, + { + method: 'DELETE', + headers: { + 'Authorization': `Bearer ${tokenData.access_token}`, + }, + } + ); + + if (!deleteResponse.ok) { + const errorData = await deleteResponse.json(); + console.error("Failed to delete user:", errorData); + return NextResponse.json({ error: "Failed to delete user" }, { status: deleteResponse.status }); + } + + return NextResponse.json({ success: true }); + } catch (error) { + console.error("Error in DELETE user:", error); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); + } +} + +export async function PUT( + req: Request, + { params }: { params: { userId: string } } +) { + const session = await getServerSession(authOptions); + + if (!session) { + return NextResponse.json({ error: "Non autorisé" }, { status: 401 }); + } + + try { + const body = 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 }); + } + + // Update user + const updateResponse = await fetch( + `${process.env.KEYCLOAK_BASE_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/users/${params.userId}`, + { + method: 'PUT', + headers: { + 'Authorization': `Bearer ${tokenData.access_token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), + } + ); + + if (!updateResponse.ok) { + const errorData = await updateResponse.json(); + console.error("Failed to update user:", errorData); + return NextResponse.json({ error: "Failed to update user" }, { status: updateResponse.status }); + } + + return NextResponse.json({ success: true }); + } catch (error) { + console.error("Error in PUT user:", error); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); + } +} \ No newline at end of file diff --git a/components/users/add-user-button.tsx b/components/users/add-user-button.tsx index 0401ca2c..131c5778 100644 --- a/components/users/add-user-button.tsx +++ b/components/users/add-user-button.tsx @@ -53,11 +53,11 @@ export function AddUserButton({ userRole, handleAddUser }: AddUserButtonProps) { const data = await response.json(); if (!response.ok) { - throw new Error(data.error || "Une erreur est survenue"); + setError(data.error || "Failed to create user"); + return; } handleAddUser(data.user); - setOpen(false); setFormData({ username: "", firstName: "", @@ -65,8 +65,10 @@ export function AddUserButton({ userRole, handleAddUser }: AddUserButtonProps) { email: "", realmRoles: "", }); - } catch (error: any) { - setError(error.message); + setOpen(false); + } catch (error) { + setError("An error occurred while creating the user"); + console.error("Error creating user:", error); } finally { setLoading(false); } diff --git a/components/users/users-table.tsx b/components/users/users-table.tsx index 58f8c868..ec14cb07 100644 --- a/components/users/users-table.tsx +++ b/components/users/users-table.tsx @@ -184,14 +184,79 @@ export function UsersTable({ userRole = [] }: UsersTableProps) { // Add this function for editing user const handleEdit = async (userId: string) => { - // TODO: Implement edit functionality - console.log("Edit user:", userId); + try { + const user = users.find(u => u.id === userId); + if (!user) return; + + const response = await fetch(`/api/users/${userId}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + firstName: user.firstName, + lastName: user.lastName, + email: user.email, + enabled: true, + }), + }); + + const data = await response.json(); + + if (!response.ok) { + console.error("Edit error:", data); + // You might want to show an error message to the user here + return; + } + + // Update the user in the local state + setUsers(prevUsers => prevUsers.map(u => + u.id === userId ? { ...u, ...data } : u + )); + + // Optional: Show success message + console.log("User updated successfully"); + } catch (error) { + console.error("Error updating user:", error); + // You might want to show an error message to the user here + } }; // Add this function for managing roles const handleManageRoles = async (userId: string) => { - // TODO: Implement role management - console.log("Manage roles for user:", userId); + try { + const user = users.find(u => u.id === userId); + if (!user) return; + + const response = await fetch(`/api/users/${userId}/roles`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + roles: user.roles, + }), + }); + + const data = await response.json(); + + if (!response.ok) { + console.error("Role update error:", data); + // You might want to show an error message to the user here + return; + } + + // Update the user's roles in the local state + setUsers(prevUsers => prevUsers.map(u => + u.id === userId ? { ...u, roles: data.roles } : u + )); + + // Optional: Show success message + console.log("User roles updated successfully"); + } catch (error) { + console.error("Error updating user roles:", error); + // You might want to show an error message to the user here + } }; if (!session) return null;