From 93aeb85a7e05631b0e8b07d0d3225e8c19866b29 Mon Sep 17 00:00:00 2001 From: Frederic Cabassut Date: Sun, 8 Mar 2026 21:32:49 +0100 Subject: [PATCH] feat(api): admin user management endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- src/routes/api/admin/users/+server.ts | 25 ++++++++++++ src/routes/api/admin/users/[id]/+server.ts | 46 ++++++++++++++++++++++ src/routes/api/auth/password/+server.ts | 24 +++++++++++ 3 files changed, 95 insertions(+) create mode 100644 src/routes/api/admin/users/+server.ts create mode 100644 src/routes/api/admin/users/[id]/+server.ts create mode 100644 src/routes/api/auth/password/+server.ts diff --git a/src/routes/api/admin/users/+server.ts b/src/routes/api/admin/users/+server.ts new file mode 100644 index 0000000..6a9176b --- /dev/null +++ b/src/routes/api/admin/users/+server.ts @@ -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 }); +}; diff --git a/src/routes/api/admin/users/[id]/+server.ts b/src/routes/api/admin/users/[id]/+server.ts new file mode 100644 index 0000000..380a3be --- /dev/null +++ b/src/routes/api/admin/users/[id]/+server.ts @@ -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 }); +}; diff --git a/src/routes/api/auth/password/+server.ts b/src/routes/api/auth/password/+server.ts new file mode 100644 index 0000000..1c51ed3 --- /dev/null +++ b/src/routes/api/auth/password/+server.ts @@ -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 }); +};