Neah/app/api/keycloak/users/route.ts
2025-05-02 11:41:43 +02:00

265 lines
7.0 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { getServerSession } from "next-auth/next";
import { authOptions } from "../../auth/[...nextauth]/route";
/**
* Get an admin token
*/
async function getAdminToken() {
try {
const tokenResponse = await fetch(
`${process.env.KEYCLOAK_ISSUER}/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;
}
return data.access_token;
} catch (error) {
console.error("Token Error:", error);
return null;
}
}
/**
* Get users from Keycloak
*/
export async function GET(request: NextRequest) {
const session = await getServerSession(authOptions);
// Check authentication
if (!session || !session.user) {
return NextResponse.json(
{ error: "Unauthorized" },
{ status: 401 }
);
}
// Only administrators can list users
const isAdmin = session.user.role?.includes('admin');
if (!isAdmin) {
return NextResponse.json(
{ error: "Forbidden: Admin role required" },
{ status: 403 }
);
}
try {
// Get admin token
const token = await getAdminToken();
if (!token) {
return NextResponse.json(
{ error: "Failed to get admin token" },
{ status: 500 }
);
}
// Parse search params
const { searchParams } = new URL(request.url);
const query = searchParams.get('query') || '';
const max = searchParams.get('max') || '100';
// Fetch users from Keycloak
const keycloakUrl = `${process.env.KEYCLOAK_BASE_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/users`;
const queryParams = new URLSearchParams();
if (query) {
queryParams.append('search', query);
}
queryParams.append('max', max);
const usersResponse = await fetch(
`${keycloakUrl}?${queryParams.toString()}`,
{
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
}
);
if (!usersResponse.ok) {
const errorData = await usersResponse.json();
return NextResponse.json(
{ error: "Failed to fetch users", details: errorData },
{ status: usersResponse.status }
);
}
const users = await usersResponse.json();
// Return user data
return NextResponse.json(users);
} catch (error) {
console.error("Error fetching users:", error);
return NextResponse.json(
{ error: "Server error fetching users" },
{ status: 500 }
);
}
}
/**
* Create a new user in Keycloak
*/
export async function POST(request: NextRequest) {
const session = await getServerSession(authOptions);
// Check authentication
if (!session || !session.user) {
return NextResponse.json(
{ error: "Unauthorized" },
{ status: 401 }
);
}
// Only administrators can create users
const isAdmin = session.user.role?.includes('admin');
if (!isAdmin) {
return NextResponse.json(
{ error: "Forbidden: Admin role required" },
{ status: 403 }
);
}
try {
// Get admin token
const token = await getAdminToken();
if (!token) {
return NextResponse.json(
{ error: "Failed to get admin token" },
{ status: 500 }
);
}
// Get user data from request
const userData = await request.json();
// Validate user data
if (!userData.username || !userData.email) {
return NextResponse.json(
{ error: "Username and email are required" },
{ status: 400 }
);
}
// Create user in Keycloak
const keycloakUrl = `${process.env.KEYCLOAK_BASE_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/users`;
const createResponse = await fetch(keycloakUrl, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
username: userData.username,
email: userData.email,
enabled: true,
emailVerified: true,
firstName: userData.firstName || '',
lastName: userData.lastName || '',
credentials: userData.password ? [
{
type: "password",
value: userData.password,
temporary: false
}
] : undefined
}),
});
if (!createResponse.ok) {
const errorData = await createResponse.json().catch(() => ({}));
return NextResponse.json(
{ error: "Failed to create user", details: errorData },
{ status: createResponse.status }
);
}
// If user was created successfully, get the user ID
const userResponse = await fetch(
`${keycloakUrl}?username=${encodeURIComponent(userData.username)}`,
{
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
}
);
if (!userResponse.ok) {
return NextResponse.json(
{ error: "User created but failed to retrieve user details" },
{ status: 207 } // Partial success
);
}
const users = await userResponse.json();
const createdUser = users[0];
// If roles are specified, assign them
if (userData.roles && Array.isArray(userData.roles) && userData.roles.length > 0) {
// Get available roles
const rolesResponse = await fetch(
`${process.env.KEYCLOAK_BASE_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/roles`,
{
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
}
);
if (rolesResponse.ok) {
const availableRoles = await rolesResponse.json();
// Filter valid roles
const validRoles = userData.roles.map((roleName: string) =>
availableRoles.find((r: any) => r.name === roleName)
).filter(Boolean);
if (validRoles.length > 0) {
// Assign roles to user
await fetch(
`${process.env.KEYCLOAK_BASE_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/users/${createdUser.id}/role-mappings/realm`,
{
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify(validRoles),
}
);
}
}
}
return NextResponse.json({
success: true,
user: createdUser
});
} catch (error) {
console.error("Error creating user:", error);
return NextResponse.json(
{ error: "Server error creating user" },
{ status: 500 }
);
}
}