harden authentication and authorization on the user API

This commit is contained in:
Jarek Krochmalski committed 2026-08-27 11:31:35 +02:00
1 parent 5adb374c0d
commit 0816672222
5 files changed
+185 -30

No files matched your search

+11 -6
View File
@@ -1,6 +1,6 @@
// v1.0.12
import '$lib/server/dns-dispatcher.js';
import { initDatabase, hasAdminUser } from '$lib/server/db';
import { initDatabase, hasAnyUser } from '$lib/server/db';
import { startSubprocesses, stopSubprocesses } from '$lib/server/subprocess-manager';
import { startScheduler } from '$lib/server/scheduler';
import { isAuthEnabled, validateSession } from '$lib/server/auth';
@@ -281,8 +281,11 @@ function isPublicPath(pathname: string): boolean {
return PUBLIC_PATHS.some(path => pathname === path || pathname.startsWith(path + '/'));
}
// Check if path is a static asset
// True only for real static files. API routes are never static, even when a path
// ends in an asset-like extension (a route parameter can), so they always go through
// the normal request handling below.
function isStaticAsset(pathname: string): boolean {
if (pathname.startsWith('/api/')) return false;
return pathname.startsWith('/_app/') ||
pathname.startsWith('/favicon') ||
pathname.endsWith('.webp') ||
@@ -377,10 +380,12 @@ export const handle: Handle = async ({ event, resolve }) => {
// If not authenticated
if (!user) {
// Special case: allow user creation when auth is enabled but no admin exists yet
// This enables the first admin user to be created during initial setup
const noAdminSetupMode = !(await hasAdminUser());
if (noAdminSetupMode && event.url.pathname === '/api/users' && event.request.method === 'POST') {
// Initial setup only: allow creating the very first account when NO user exists
// yet (there is no one to authenticate as). Once any account exists, creation
// goes through the normal authenticated path. Bounding this on any-user (not the
// Admin role) keeps it from reopening when accounts exist but no admin does.
const firstRunSetup = !(await hasAnyUser());
if (firstRunSetup && event.url.pathname === '/api/users' && event.request.method === 'POST') {
return requestContext.run(ctx, async () => compressResponse(event.request, await resolve(event)));
}