diff --git a/bun.lock b/bun.lock index d6767cd..65e4524 100644 --- a/bun.lock +++ b/bun.lock @@ -6,6 +6,7 @@ "name": "trips", "dependencies": { "@auth/sveltekit": "^1.11.1", + "argon2": "^0.41.1", }, "devDependencies": { "@biomejs/biome": "^2.4.4", @@ -153,6 +154,8 @@ "@panva/hkdf": ["@panva/hkdf@1.2.1", "", {}, "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw=="], + "@phc/format": ["@phc/format@1.0.0", "", {}, "sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ=="], + "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="], "@rollup/plugin-commonjs": ["@rollup/plugin-commonjs@29.0.0", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "commondir": "^1.0.1", "estree-walker": "^2.0.2", "fdir": "^6.2.0", "is-reference": "1.2.1", "magic-string": "^0.30.3", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^2.68.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-U2YHaxR2cU/yAiwKJtJRhnyLk7cifnQw0zUpISsocBDoHDJn+HTV74ABqnwr5bEgWUwFZC9oFL6wLe21lHu5eQ=="], @@ -315,6 +318,8 @@ "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], + "argon2": ["argon2@0.41.1", "", { "dependencies": { "@phc/format": "^1.0.0", "node-addon-api": "^8.1.0", "node-gyp-build": "^4.8.1" } }, "sha512-dqCW8kJXke8Ik+McUcMDltrbuAWETPyU6iq+4AhxqKphWi7pChB/Zgd/Tp/o8xRLbg8ksMj46F/vph9wnxpTzQ=="], + "aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], @@ -509,6 +514,10 @@ "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + "node-addon-api": ["node-addon-api@8.5.0", "", {}, "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A=="], + + "node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="], + "oauth4webapi": ["oauth4webapi@3.8.5", "", {}, "sha512-A8jmyUckVhRJj5lspguklcl90Ydqk61H3dcU0oLhH3Yv13KpAliKTt5hknpGGPZSSfOwGyraNEFmofDYH+1kSg=="], "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], diff --git a/src/lib/server/admin/auth.test.ts b/src/lib/server/admin/auth.test.ts new file mode 100644 index 0000000..b80dc55 --- /dev/null +++ b/src/lib/server/admin/auth.test.ts @@ -0,0 +1,81 @@ +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { setupTestDb } from '../../../tests/helpers.js'; +import type { Database } from '../db/types.js'; +import { upsertUserFromAuth } from '../users.js'; +import { isAdminUser, requireAdmin } from './auth.js'; + +let database: Database; +const originalAdminUserIds = process.env.ADMIN_USER_IDS; + +beforeEach(() => { + database = setupTestDb(); + process.env.ADMIN_USER_IDS = ''; +}); + +afterEach(() => { + process.env.ADMIN_USER_IDS = originalAdminUserIds; + database.close(); +}); + +describe('isAdminUser', () => { + it('allows direct ID matches', () => { + process.env.ADMIN_USER_IDS = 'oidc-sub-123'; + expect(isAdminUser('oidc-sub-123')).toBe(true); + }); + + it('allows username matches for known users', () => { + upsertUserFromAuth({ + id: 'oidc-sub-123', + username: 'shaun', + fullName: 'Shaun Campbell', + email: 'shaun@example.com' + }); + process.env.ADMIN_USER_IDS = 'shaun'; + expect(isAdminUser('oidc-sub-123')).toBe(true); + }); + + it('allows email matches for known users', () => { + upsertUserFromAuth({ + id: 'oidc-sub-123', + username: 'shaun', + fullName: 'Shaun Campbell', + email: 'shaun@example.com' + }); + process.env.ADMIN_USER_IDS = 'shaun@example.com'; + expect(isAdminUser('oidc-sub-123')).toBe(true); + }); + + it('allows email local-part matches from session user', () => { + process.env.ADMIN_USER_IDS = 'shaun'; + expect(isAdminUser({ id: 'oidc-sub-123', email: 'shaun@example.com' })).toBe(true); + }); +}); + +describe('requireAdmin', () => { + it('throws when not authenticated', () => { + process.env.ADMIN_USER_IDS = 'shaun'; + expect(() => requireAdmin(undefined)).toThrow('Not authenticated'); + }); + + it('throws when user is not admin', () => { + process.env.ADMIN_USER_IDS = 'shaun'; + expect(() => requireAdmin('someone-else')).toThrow('Admin access required'); + }); + + it('does not throw when username match grants admin access', () => { + upsertUserFromAuth({ + id: 'oidc-sub-123', + username: 'shaun', + fullName: 'Shaun Campbell' + }); + process.env.ADMIN_USER_IDS = 'shaun'; + expect(() => requireAdmin('oidc-sub-123')).not.toThrow(); + }); + + it('does not throw when session user email local-part matches', () => { + process.env.ADMIN_USER_IDS = 'shaun'; + expect(() => + requireAdmin({ id: 'oidc-sub-123', email: 'shaun@example.com', name: 'Shaun Campbell' }) + ).not.toThrow(); + }); +}); diff --git a/src/lib/server/admin/auth.ts b/src/lib/server/admin/auth.ts index ec6df08..95c31a7 100644 --- a/src/lib/server/admin/auth.ts +++ b/src/lib/server/admin/auth.ts @@ -1,12 +1,73 @@ import { env } from '$env/dynamic/private'; +import { db } from '$lib/server/db/index.js'; -export function requireAdmin(userId: string | undefined): void { - if (!userId) throw new Error('Not authenticated'); - const adminIds = (env.ADMIN_USER_IDS ?? '') +const getAdminIdentifiers = (): string[] => + (process.env.ADMIN_USER_IDS ?? env.ADMIN_USER_IDS ?? '') .split(',') .map((s) => s.trim()) .filter(Boolean); - if (adminIds.length === 0 || !adminIds.includes(userId)) { + +type AdminPrincipal = + | string + | { + id?: string | null; + name?: string | null; + email?: string | null; + } + | undefined; + +const normalize = (value: string): string => value.trim().toLowerCase(); + +const getPrincipalValues = (principal: AdminPrincipal): { userId?: string; values: string[] } => { + if (!principal) return { values: [] }; + if (typeof principal === 'string') { + const value = principal.trim(); + return value ? { userId: value, values: [value] } : { values: [] }; + } + + const values = [principal.id, principal.name, principal.email] + .filter((value): value is string => Boolean(value?.trim())) + .map((value) => value.trim()); + const localPart = principal.email?.split('@')[0]?.trim(); + if (localPart) values.push(localPart); + + const userId = principal.id?.trim(); + return { userId, values }; +}; + +export function isAdminUser(principal: AdminPrincipal): boolean { + const { userId, values } = getPrincipalValues(principal); + if (values.length === 0) return false; + + const adminIds = getAdminIdentifiers(); + if (adminIds.length === 0) return false; + + const normalizedAdminIds = new Set(adminIds.map(normalize)); + for (const value of values) { + if (adminIds.includes(value) || normalizedAdminIds.has(normalize(value))) return true; + } + + if (!userId) return false; + + const user = db.get<{ username: string; email: string | null }>( + 'SELECT username, email FROM users WHERE id = ?', + [userId] + ); + if (!user) return false; + + const dbValues = [user.username, user.email, user.email?.split('@')[0]] + .filter((value): value is string => Boolean(value?.trim())) + .map((value) => value.trim()); + for (const value of dbValues) { + if (adminIds.includes(value) || normalizedAdminIds.has(normalize(value))) return true; + } + + return false; +} + +export function requireAdmin(principal: AdminPrincipal): void { + if (!principal) throw new Error('Not authenticated'); + if (!isAdminUser(principal)) { throw new Error('Admin access required'); } } diff --git a/src/routes/(protected)/+layout.server.ts b/src/routes/(protected)/+layout.server.ts index f4335d1..fa16498 100644 --- a/src/routes/(protected)/+layout.server.ts +++ b/src/routes/(protected)/+layout.server.ts @@ -1,6 +1,6 @@ import { redirect } from '@sveltejs/kit'; import { base } from '$app/paths'; -import { env } from '$env/dynamic/private'; +import { isAdminUser } from '$lib/server/admin/auth.js'; import type { LayoutServerLoad } from './$types'; export const load: LayoutServerLoad = async (event) => { @@ -9,11 +9,7 @@ export const load: LayoutServerLoad = async (event) => { redirect(303, `${base}/login`); } - const adminIds = (env.ADMIN_USER_IDS ?? '') - .split(',') - .map((s) => s.trim()) - .filter(Boolean); - const isAdmin = adminIds.length > 0 && session.user.id && adminIds.includes(session.user.id); + const isAdmin = isAdminUser(session.user); return { session, isAdmin }; }; diff --git a/src/routes/(protected)/admin/api/airlines/+server.ts b/src/routes/(protected)/admin/api/airlines/+server.ts index dfaef7a..8818491 100644 --- a/src/routes/(protected)/admin/api/airlines/+server.ts +++ b/src/routes/(protected)/admin/api/airlines/+server.ts @@ -4,13 +4,13 @@ import { requireAdmin } from '$lib/server/admin/auth.js'; import * as data from '$lib/server/admin/data.js'; export const GET: RequestHandler = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const q = event.url.searchParams.get('q') ?? undefined; return json(data.listAirlines(q)); }; export const POST: RequestHandler = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const body = await event.request.json(); const { name, country, country_code, iata_code, icao_code } = body as { name?: string; @@ -32,7 +32,7 @@ export const POST: RequestHandler = async (event) => { }; export const PATCH: RequestHandler = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const body = await event.request.json(); const { id, name, country, country_code, iata_code, icao_code } = body as { id?: number; @@ -55,7 +55,7 @@ export const PATCH: RequestHandler = async (event) => { }; export const DELETE: RequestHandler = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const id = parseInt(event.url.searchParams.get('id') ?? ''); if (isNaN(id)) return json({ error: 'id required' }, { status: 400 }); data.deleteAirline(id); diff --git a/src/routes/(protected)/admin/api/airports/+server.ts b/src/routes/(protected)/admin/api/airports/+server.ts index a98347e..10a7fb2 100644 --- a/src/routes/(protected)/admin/api/airports/+server.ts +++ b/src/routes/(protected)/admin/api/airports/+server.ts @@ -4,13 +4,13 @@ import { requireAdmin } from '$lib/server/admin/auth.js'; import * as data from '$lib/server/admin/data.js'; export const GET: RequestHandler = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const q = event.url.searchParams.get('q') ?? undefined; return json(data.listAirports(q)); }; export const POST: RequestHandler = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const body = await event.request.json(); const { name, country, country_code, iata_code, icao_code, city, latitude, longitude, timezone } = body as { @@ -43,7 +43,7 @@ export const POST: RequestHandler = async (event) => { }; export const PATCH: RequestHandler = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const body = await event.request.json(); const { id, @@ -87,7 +87,7 @@ export const PATCH: RequestHandler = async (event) => { }; export const DELETE: RequestHandler = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const id = parseInt(event.url.searchParams.get('id') ?? ''); if (isNaN(id)) return json({ error: 'id required' }, { status: 400 }); data.deleteAirport(id); diff --git a/src/routes/(protected)/admin/api/cities/+server.ts b/src/routes/(protected)/admin/api/cities/+server.ts index ccf656c..8aa8a6c 100644 --- a/src/routes/(protected)/admin/api/cities/+server.ts +++ b/src/routes/(protected)/admin/api/cities/+server.ts @@ -4,14 +4,14 @@ import { requireAdmin } from '$lib/server/admin/auth.js'; import * as data from '$lib/server/admin/data.js'; export const GET: RequestHandler = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const q = event.url.searchParams.get('q') ?? undefined; const countryCode = event.url.searchParams.get('country_code') ?? undefined; return json(data.listCities(q, countryCode)); }; export const POST: RequestHandler = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const body = await event.request.json(); const { name, country, country_code, population } = body as { name?: string; @@ -26,7 +26,7 @@ export const POST: RequestHandler = async (event) => { }; export const PATCH: RequestHandler = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const body = await event.request.json(); const { id, name, country, country_code, population } = body as { id?: number; @@ -43,7 +43,7 @@ export const PATCH: RequestHandler = async (event) => { }; export const DELETE: RequestHandler = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const id = parseInt(event.url.searchParams.get('id') ?? ''); if (isNaN(id)) return json({ error: 'id required' }, { status: 400 }); data.deleteCity(id); diff --git a/src/routes/(protected)/admin/api/countries/+server.ts b/src/routes/(protected)/admin/api/countries/+server.ts index 80f25ba..52d7fb6 100644 --- a/src/routes/(protected)/admin/api/countries/+server.ts +++ b/src/routes/(protected)/admin/api/countries/+server.ts @@ -4,13 +4,13 @@ import { requireAdmin } from '$lib/server/admin/auth.js'; import * as data from '$lib/server/admin/data.js'; export const GET: RequestHandler = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const q = event.url.searchParams.get('q') ?? undefined; return json(data.listCountries(q)); }; export const POST: RequestHandler = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const body = await event.request.json(); const { name, country_code } = body as { name?: string; country_code?: string }; if (!name?.trim() || !country_code?.trim()) { @@ -20,7 +20,7 @@ export const POST: RequestHandler = async (event) => { }; export const PATCH: RequestHandler = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const body = await event.request.json(); const { id, name, country_code } = body as { id?: number; name?: string; country_code?: string }; if (id == null || !name?.trim() || !country_code?.trim()) { @@ -31,7 +31,7 @@ export const PATCH: RequestHandler = async (event) => { }; export const DELETE: RequestHandler = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const id = parseInt(event.url.searchParams.get('id') ?? ''); if (isNaN(id)) return json({ error: 'id required' }, { status: 400 }); data.deleteCountry(id); diff --git a/src/routes/(protected)/admin/api/operator-tour-days/+server.ts b/src/routes/(protected)/admin/api/operator-tour-days/+server.ts index 4d44a50..0512d07 100644 --- a/src/routes/(protected)/admin/api/operator-tour-days/+server.ts +++ b/src/routes/(protected)/admin/api/operator-tour-days/+server.ts @@ -4,14 +4,14 @@ import { requireAdmin } from '$lib/server/admin/auth.js'; import * as data from '$lib/server/admin/data.js'; export const GET: RequestHandler = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const tourId = parseInt(event.url.searchParams.get('operator_tour_id') ?? ''); if (isNaN(tourId)) return json({ error: 'operator_tour_id required' }, { status: 400 }); return json(data.listDaysForOperatorTour(tourId)); }; export const POST: RequestHandler = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const body = await event.request.json(); const { operator_tour_id, title, notes } = body as { operator_tour_id?: number; @@ -25,7 +25,7 @@ export const POST: RequestHandler = async (event) => { }; export const PATCH: RequestHandler = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const body = await event.request.json(); const { id, title, notes } = body as { id?: number; title?: string; notes?: string }; if (id == null) return json({ error: 'id is required' }, { status: 400 }); @@ -34,7 +34,7 @@ export const PATCH: RequestHandler = async (event) => { }; export const DELETE: RequestHandler = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const id = parseInt(event.url.searchParams.get('id') ?? ''); if (isNaN(id)) return json({ error: 'id required' }, { status: 400 }); data.deleteOperatorTourDay(id); diff --git a/src/routes/(protected)/admin/api/operator-tours/+server.ts b/src/routes/(protected)/admin/api/operator-tours/+server.ts index 4c13a65..b0511a8 100644 --- a/src/routes/(protected)/admin/api/operator-tours/+server.ts +++ b/src/routes/(protected)/admin/api/operator-tours/+server.ts @@ -4,14 +4,14 @@ import { requireAdmin } from '$lib/server/admin/auth.js'; import * as data from '$lib/server/admin/data.js'; export const GET: RequestHandler = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const operatorId = parseInt(event.url.searchParams.get('operator_id') ?? ''); if (isNaN(operatorId)) return json({ error: 'operator_id required' }, { status: 400 }); return json(data.listToursForOperator(operatorId)); }; export const POST: RequestHandler = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const body = await event.request.json(); const { operator_id, name } = body as { operator_id?: number; name?: string }; if (operator_id == null || !name?.trim()) { @@ -21,7 +21,7 @@ export const POST: RequestHandler = async (event) => { }; export const PATCH: RequestHandler = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const body = await event.request.json(); const { id, name } = body as { id?: number; name?: string }; if (id == null || !name?.trim()) { @@ -32,7 +32,7 @@ export const PATCH: RequestHandler = async (event) => { }; export const DELETE: RequestHandler = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const id = parseInt(event.url.searchParams.get('id') ?? ''); if (isNaN(id)) return json({ error: 'id required' }, { status: 400 }); data.deleteOperatorTour(id); diff --git a/src/routes/(protected)/admin/api/provider-search/+server.ts b/src/routes/(protected)/admin/api/provider-search/+server.ts index 9dabca9..7edf9af 100644 --- a/src/routes/(protected)/admin/api/provider-search/+server.ts +++ b/src/routes/(protected)/admin/api/provider-search/+server.ts @@ -5,7 +5,7 @@ import { listTourOperators } from '$lib/server/admin/data.js'; import { getProviderForOperator } from '$lib/server/admin/providers/index.js'; export const GET: RequestHandler = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const operatorId = parseInt(event.url.searchParams.get('operator_id') ?? ''); if (isNaN(operatorId)) return json({ error: 'operator_id required' }, { status: 400 }); diff --git a/src/routes/(protected)/admin/api/tour-operators/+server.ts b/src/routes/(protected)/admin/api/tour-operators/+server.ts index 96f1f89..21c056e 100644 --- a/src/routes/(protected)/admin/api/tour-operators/+server.ts +++ b/src/routes/(protected)/admin/api/tour-operators/+server.ts @@ -4,13 +4,13 @@ import { requireAdmin } from '$lib/server/admin/auth.js'; import * as data from '$lib/server/admin/data.js'; export const GET: RequestHandler = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const q = event.url.searchParams.get('q') ?? undefined; return json(data.listTourOperators(q)); }; export const POST: RequestHandler = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const body = await event.request.json(); const { name, website, highlight_color } = body as { name?: string; @@ -24,7 +24,7 @@ export const POST: RequestHandler = async (event) => { }; export const PATCH: RequestHandler = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const body = await event.request.json(); const { id, name, website, highlight_color } = body as { id?: number; @@ -40,7 +40,7 @@ export const PATCH: RequestHandler = async (event) => { }; export const DELETE: RequestHandler = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const id = parseInt(event.url.searchParams.get('id') ?? ''); if (isNaN(id)) return json({ error: 'id required' }, { status: 400 }); data.deleteTourOperator(id); diff --git a/src/routes/(protected)/admin/api/users/+server.ts b/src/routes/(protected)/admin/api/users/+server.ts index f8257f1..378fb6b 100644 --- a/src/routes/(protected)/admin/api/users/+server.ts +++ b/src/routes/(protected)/admin/api/users/+server.ts @@ -4,12 +4,12 @@ import { requireAdmin } from '$lib/server/admin/auth.js'; import { deleteUser, listUsers, updateUser } from '$lib/server/users.js'; export const GET: RequestHandler = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); return json(listUsers()); }; export const PATCH: RequestHandler = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const body = await event.request.json(); const { id, username, full_name, email } = body as { id?: string; @@ -25,7 +25,7 @@ export const PATCH: RequestHandler = async (event) => { }; export const DELETE: RequestHandler = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const id = event.url.searchParams.get('id')?.trim(); if (!id) return json({ error: 'id required' }, { status: 400 }); deleteUser(id); diff --git a/src/routes/(protected)/admin/tour-operators/[operatorId]/+page.server.ts b/src/routes/(protected)/admin/tour-operators/[operatorId]/+page.server.ts index 4c844b1..8871e8c 100644 --- a/src/routes/(protected)/admin/tour-operators/[operatorId]/+page.server.ts +++ b/src/routes/(protected)/admin/tour-operators/[operatorId]/+page.server.ts @@ -5,7 +5,7 @@ import { getProviderForOperator } from '$lib/server/admin/providers/index.js'; import type { PageServerLoad, Actions } from './$types'; export const load: PageServerLoad = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const operatorId = parseInt(event.params.operatorId); if (isNaN(operatorId)) error(404, 'Not found'); @@ -27,7 +27,7 @@ export const load: PageServerLoad = async (event) => { export const actions: Actions = { addTour: async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const operatorId = parseInt(event.params.operatorId); if (isNaN(operatorId)) return fail(400, { error: 'Invalid operator ID' }); @@ -45,7 +45,7 @@ export const actions: Actions = { }, editTour: async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const formData = await event.request.formData(); const id = parseInt(formData.get('id') as string); @@ -63,7 +63,7 @@ export const actions: Actions = { }, deleteTour: async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const formData = await event.request.formData(); const id = parseInt(formData.get('id') as string); @@ -78,7 +78,7 @@ export const actions: Actions = { }, importTour: async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const operatorId = parseInt(event.params.operatorId); if (isNaN(operatorId)) return fail(400, { error: 'Invalid operator ID' }); diff --git a/src/routes/(protected)/admin/tour-operators/[operatorId]/tours/[tourId]/+page.server.ts b/src/routes/(protected)/admin/tour-operators/[operatorId]/tours/[tourId]/+page.server.ts index 82df5b9..3c63b8f 100644 --- a/src/routes/(protected)/admin/tour-operators/[operatorId]/tours/[tourId]/+page.server.ts +++ b/src/routes/(protected)/admin/tour-operators/[operatorId]/tours/[tourId]/+page.server.ts @@ -4,7 +4,7 @@ import * as data from '$lib/server/admin/data.js'; import type { PageServerLoad, Actions } from './$types'; export const load: PageServerLoad = async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const operatorId = parseInt(event.params.operatorId); const tourId = parseInt(event.params.tourId); @@ -27,7 +27,7 @@ export const load: PageServerLoad = async (event) => { export const actions: Actions = { addDay: async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const tourId = parseInt(event.params.tourId); if (isNaN(tourId)) return fail(400, { error: 'Invalid tour ID' }); @@ -45,7 +45,7 @@ export const actions: Actions = { }, editDay: async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const formData = await event.request.formData(); const id = parseInt(formData.get('id') as string); @@ -63,7 +63,7 @@ export const actions: Actions = { }, deleteDay: async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const formData = await event.request.formData(); const id = parseInt(formData.get('id') as string); @@ -78,7 +78,7 @@ export const actions: Actions = { }, addDayPlan: async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const formData = await event.request.formData(); const dayId = parseInt(formData.get('dayId') as string); @@ -247,7 +247,7 @@ export const actions: Actions = { }, editDayPlan: async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const formData = await event.request.formData(); const id = parseInt(formData.get('id') as string); @@ -310,7 +310,7 @@ export const actions: Actions = { }, deleteDayPlan: async (event) => { - requireAdmin((await event.locals.auth())?.user?.id); + requireAdmin((await event.locals.auth())?.user); const formData = await event.request.formData(); const id = parseInt(formData.get('id') as string); diff --git a/src/routes/login/+page.server.test.ts b/src/routes/login/page.server.test.ts similarity index 100% rename from src/routes/login/+page.server.test.ts rename to src/routes/login/page.server.test.ts