feat(api): admin user management endpoints

- GET/POST /api/admin/users — list and create users (admin only)
- PATCH/DELETE /api/admin/users/[id] — update role/reset password, delete user
- PUT /api/auth/password — change own password
All admin routes guarded by requireAdmin().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Frederic Cabassut
2026-03-09 01:10:37 +01:00
committed by Fred Cabassut
co-authored by Claude Opus 4.6
parent 133690994b
commit 33864d9355
3 changed files with 95 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
import { json, error } from '@sveltejs/kit';
import type { RequestHandler } from './$types.js';
import { requireAdmin } from '$lib/server/api-utils.js';
import { listUsers, createUser } from '$lib/server/auth.js';
export const GET: RequestHandler = async (event) => {
requireAdmin(event);
return json(listUsers());
};
export const POST: RequestHandler = async ({ request, ...event }) => {
requireAdmin(event);
const { email, displayName, password, role } = await request.json();
if (!email || !password) {
throw error(400, 'Email and password are required');
}
if (password.length < 8) {
throw error(400, 'Password must be at least 8 characters');
}
const user = await createUser(email, displayName || email.split('@')[0], password, role || 'user');
return json(user, { status: 201 });
};
@@ -0,0 +1,46 @@
import { json, error } from '@sveltejs/kit';
import type { RequestHandler } from './$types.js';
import { requireAdmin } from '$lib/server/api-utils.js';
import { getUser, deleteUser, updateUserRole, resetPassword } from '$lib/server/auth.js';
export const PATCH: RequestHandler = async ({ params, request, ...event }) => {
const admin = requireAdmin(event);
const userId = parseInt(params.id, 10);
const body = await request.json();
const user = getUser(userId);
if (!user) throw error(404, 'User not found');
if (body.role !== undefined) {
// Prevent admin from demoting themselves
if (userId === admin.id && body.role !== 'admin') {
throw error(400, 'Cannot change your own role');
}
updateUserRole(userId, body.role);
}
if (body.newPassword !== undefined) {
if (body.newPassword.length < 8) {
throw error(400, 'Password must be at least 8 characters');
}
await resetPassword(userId, body.newPassword);
}
const updated = getUser(userId);
return json(updated);
};
export const DELETE: RequestHandler = async ({ params, ...event }) => {
const admin = requireAdmin(event);
const userId = parseInt(params.id, 10);
if (userId === admin.id) {
throw error(400, 'Cannot delete your own account');
}
const user = getUser(userId);
if (!user) throw error(404, 'User not found');
await deleteUser(userId);
return json({ success: true });
};
+24
View File
@@ -0,0 +1,24 @@
import { json, error } from '@sveltejs/kit';
import type { RequestHandler } from './$types.js';
import { getUserId } from '$lib/server/api-utils.js';
import { changePassword } from '$lib/server/auth.js';
export const PUT: RequestHandler = async ({ request, ...event }) => {
const userId = getUserId(event);
const { currentPassword, newPassword } = await request.json();
if (!currentPassword || !newPassword) {
throw error(400, 'Current password and new password are required');
}
if (newPassword.length < 8) {
throw error(400, 'New password must be at least 8 characters');
}
const success = await changePassword(userId, currentPassword, newPassword);
if (!success) {
throw error(401, 'Current password is incorrect');
}
return json({ success: true });
};