Add local authentication option (#37)
All checks were successful
Build and Push Image / docker-build-and-push (push) Successful in 2m20s

Co-authored-by: Shaun Campbell <shaun@campbellwireless.net>
Reviewed-on: #37
Co-authored-by: AI Agent <ai-agent@campbellwireless.net>
Co-committed-by: AI Agent <ai-agent@campbellwireless.net>
This commit was merged in pull request #37.
This commit is contained in:
2026-02-22 05:17:45 +00:00
committed by shaun
parent 42fe36ca86
commit fc8e80186d
26 changed files with 695 additions and 71 deletions

View File

@@ -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 };
};

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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 });

View File

@@ -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);

View File

@@ -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);

View File

@@ -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' });

View File

@@ -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);