diff --git a/docker/node.Dockerfile b/docker/node.Dockerfile index b64ef2a..75e9bd8 100644 --- a/docker/node.Dockerfile +++ b/docker/node.Dockerfile @@ -7,8 +7,6 @@ RUN corepack enable FROM base AS build RUN pnpm i --frozen-lockfile COPY . . -# Ugly workaround some build bug that tries to connect to the DB while bundling -ENV STAGE build RUN pnpm run build FROM base AS deps diff --git a/src/app.d.ts b/src/app.d.ts index 61fd649..5de50c2 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -1,9 +1,11 @@ +import type { Database } from '$lib/server/db/connect'; import type { User } from '$lib/server/db/schema/user'; import type { Session } from '$lib/server/db/schema/session'; declare global { namespace App { interface Locals { + database: Database; user: User | null; session: Session | null; } diff --git a/src/hooks.server.ts b/src/hooks.server.ts index fb8ee5d..ae602d1 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,9 +1,16 @@ +import { connectDatabase } from '$lib/server/db/connect'; import { validateSessionToken } from '$lib/server/db/session'; import { setSessionTokenCookie, deleteSessionTokenCookie } from '$lib/server/session'; import type { Handle } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit'; +// Connect to database on startup +const { database } = await connectDatabase(); + export const handle: Handle = async ({ event, resolve }) => { + // Set the database in locals for the rest of the request + event.locals.database = database; + // Allow access to the admin pages without authentication (secret is required) if (event.url.pathname.startsWith('/admin')) { return resolve(event); @@ -23,7 +30,7 @@ export const handle: Handle = async ({ event, resolve }) => { } // Validate session token - const { session, user } = await validateSessionToken(token); + const { session, user } = await validateSessionToken(database, token); if (session !== null) { // If session is valid, ensure the token is up-to-date setSessionTokenCookie(event, token, session.timestamp); diff --git a/src/lib/server/db/building.ts b/src/lib/server/db/building.ts index 5ea18ab..847ea91 100644 --- a/src/lib/server/db/building.ts +++ b/src/lib/server/db/building.ts @@ -1,12 +1,12 @@ +import type { Database } from './connect'; import type { Building } from '$lib/types/db'; -import { DB as db } from './connect'; import { building } from './schema/building'; -export async function getBuildings(): Promise { +export async function getBuildings(db: Database): Promise { return await db.select().from(building).orderBy(building.name); } -export async function createBuilding(name: string): Promise { +export async function createBuilding(db: Database, name: string): Promise { // Assert that name is valid if (name === null || name === undefined || name === '') { throw new Error('Invalid name'); diff --git a/src/lib/server/db/connect.ts b/src/lib/server/db/connect.ts index 9e09ae0..23c4b59 100644 --- a/src/lib/server/db/connect.ts +++ b/src/lib/server/db/connect.ts @@ -1,23 +1,18 @@ -import { type PostgresJsDatabase } from 'drizzle-orm/postgres-js'; -import postgres from 'postgres'; import { env } from '$env/dynamic/private'; -import { connectDatabaseWithURL } from './connect_generic'; +import { sleep } from '$lib/utils/sleep'; +import postgres from 'postgres'; +import { drizzle } from 'drizzle-orm/postgres-js'; +import { migrate } from 'drizzle-orm/postgres-js/migrator'; +import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'; export type Database = PostgresJsDatabase>; export type Client = postgres.Sql<{}>; // eslint-disable-line @typescript-eslint/no-empty-object-type // Connects to the database using the DATABASE_URL environment variable export async function connectDatabase(): Promise<{ - database: Database | null; - client: Client | null; + database: Database; + client: Client; }> { - const stage = env.STAGE; - // WARN: Return immediately if the stage is build - // TODO: Why is this needed JS lords??? - if (stage === 'build') { - return { database: null, client: null }; - } - const dbUrl = env.DATABASE_URL; // Assert that the DATABASE_URL environment variable is set if (!dbUrl) throw new Error('DATABASE_URL is not set'); @@ -29,5 +24,50 @@ export async function connectDatabase(): Promise<{ return await connectDatabaseWithURL(dbUrl, migrationsPath); } -// WARN: Forces Database | null to become Database (problems with building) -export const DB = (await connectDatabase()).database as Database; +// Connects to the database using the provided URL +export async function connectDatabaseWithURL( + dbUrl: string, + migrationsPath: string +): Promise<{ database: Database; client: Client }> { + // Assert non-null and non-empty dbUrl and migrationsPath + if (!dbUrl) throw new Error('dbUrl is required'); + if (!migrationsPath) throw new Error('migrationsPath is required'); + + console.log('Connecting to database'); + console.debug('DEBUG: Database url (contains secret):', dbUrl); + console.log('Running migrations from:', migrationsPath); + + const maxAttempts = 10; // Maximum retry attempts (1 attempt per second) + const delayMs = 1000; // Delay between attempts (1 second) + + let attempts = 0; + + // Helper function to attempt the database connection + const tryConnect = async (): Promise<{ database: Database; client: Client }> => { + attempts++; + try { + // Connect to the database + const client = postgres(dbUrl); + const database = drizzle(client); + + // Run migrations + await migrate(database, { migrationsFolder: migrationsPath }); + + console.log('Database connection successful after', attempts, 'attempts'); + + return { database, client }; + } catch (err) { + if (attempts >= maxAttempts) { + throw new Error(`Database connection failed after 10 seconds: ${(err as Error).message}`); + } + console.log( + `Connection attempt ${attempts} failed. Retrying in ${delayMs / 1000} second(s)...` + ); + await sleep(delayMs); // Wait before retrying + return tryConnect(); // Recursively retry the connection + } + }; + + // Start the connection attempt + return tryConnect(); +} diff --git a/src/lib/server/db/connect_generic.ts b/src/lib/server/db/connect_generic.ts deleted file mode 100644 index bbf7369..0000000 --- a/src/lib/server/db/connect_generic.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type { Client, Database } from './connect'; -import postgres from 'postgres'; -import { drizzle } from 'drizzle-orm/postgres-js'; -import { migrate } from 'drizzle-orm/postgres-js/migrator'; -import { sleep } from '$lib/utils/sleep'; - -// Connects to the database using the provided URL -export async function connectDatabaseWithURL( - dbUrl: string, - migrationsPath: string -): Promise<{ database: Database; client: Client }> { - // Assert non-null and non-empty dbUrl and migrationsPath - if (!dbUrl) throw new Error('dbUrl is required'); - if (!migrationsPath) throw new Error('migrationsPath is required'); - - console.log('Connecting to database'); - console.debug('DEBUG: Database url (contains secret):', dbUrl); - console.log('Running migrations from:', migrationsPath); - - const maxAttempts = 10; // Maximum retry attempts (1 attempt per second) - const delayMs = 1000; // Delay between attempts (1 second) - - let attempts = 0; - - // Helper function to attempt the database connection - const tryConnect = async (): Promise<{ database: Database; client: Client }> => { - attempts++; - try { - // Connect to the database - const client = postgres(dbUrl); - const database = drizzle(client); - - // Run migrations - await migrate(database, { migrationsFolder: migrationsPath }); - - console.log('Database connection successful after', attempts, 'attempts'); - - return { database, client }; - } catch (err) { - if (attempts >= maxAttempts) { - throw new Error(`Database connection failed after 10 seconds: ${(err as Error).message}`); - } - console.log( - `Connection attempt ${attempts} failed. Retrying in ${delayMs / 1000} second(s)...` - ); - await sleep(delayMs); // Wait before retrying - return tryConnect(); // Recursively retry the connection - } - }; - - // Start the connection attempt - return tryConnect(); -} diff --git a/src/lib/server/db/department.ts b/src/lib/server/db/department.ts index 28b6ac9..3e38b75 100644 --- a/src/lib/server/db/department.ts +++ b/src/lib/server/db/department.ts @@ -1,12 +1,12 @@ +import type { Database } from './connect'; import type { Department } from '$lib/types/db'; -import { DB as db } from './connect'; import { department } from './schema/department'; -export async function getDepartments(): Promise { +export async function getDepartments(db: Database): Promise { return await db.select().from(department); } -export async function createDepartment(name: string): Promise { +export async function createDepartment(db: Database, name: string): Promise { // Assert that name is valid if (name === null || name === undefined || name === '') { throw new Error('Invalid name'); diff --git a/src/lib/server/db/person.ts b/src/lib/server/db/person.ts index 9683b79..dd1bdbc 100644 --- a/src/lib/server/db/person.ts +++ b/src/lib/server/db/person.ts @@ -1,16 +1,17 @@ +import type { Database } from './connect'; import { or, eq, and, max, gt, count, isNull } from 'drizzle-orm'; import { person, personEntry, personExit } from './schema/person'; import { StateInside, StateOutside, type State } from '$lib/types/state'; import { fuzzySearchFilters } from './fuzzysearch'; import { sqlConcat, sqlLeast, sqlLevenshteinDistance } from './utils'; import { isInside } from '../isInside'; -import { DB as db } from './connect'; import { capitalizeString, sanitizeString } from '$lib/utils/sanitize'; import { building } from './schema/building'; import { isPersonType, type PersonType } from '$lib/types/person'; // Gets all persons using optional filters export async function getPersons( + db: Database, limit: number, offset: number, searchQuery?: string @@ -176,7 +177,7 @@ export async function getPersons( } // Gets the count of all persons per department -export async function getPersonsCountPerDepartment(): Promise< +export async function getPersonsCountPerDepartment(db: Database): Promise< { type: PersonType; department: string; @@ -209,7 +210,7 @@ export async function getPersonsCountPerDepartment(): Promise< } // Gets the count of all persons that are inside, per building -export async function getPersonsCountPerBuilding(): Promise< +export async function getPersonsCountPerBuilding(db: Database): Promise< { type: PersonType | null; building: string; @@ -263,6 +264,7 @@ export async function getPersonsCountPerBuilding(): Promise< // Creates a person and the entry timestamp export async function createPerson( + db: Database, identifierD: string, type: PersonType, fnameD: string, @@ -344,6 +346,7 @@ export async function createPerson( // Toggles the state of a person (inside to outside and vice versa) export async function togglePersonState( + db: Database, id: number, building: string, creator: string @@ -401,7 +404,7 @@ export async function togglePersonState( } } -export async function getPersonTypes(): Promise { +export async function getPersonTypes(db: Database): Promise { try { const types = await db .selectDistinctOn([person.type], { type: person.type }) @@ -418,7 +421,11 @@ export async function getPersonTypes(): Promise { } } -export async function removePersonsFromBuilding(building: string, type: string): Promise { +export async function removePersonsFromBuilding( + db: Database, + building: string, + type: string +): Promise { try { return await db.transaction(async (tx) => { const personsInside = await tx diff --git a/src/lib/server/db/session.ts b/src/lib/server/db/session.ts index 335a98c..485f3a9 100644 --- a/src/lib/server/db/session.ts +++ b/src/lib/server/db/session.ts @@ -1,22 +1,16 @@ +import type { Database } from './connect'; import { and, desc, eq, notInArray } from 'drizzle-orm'; -import { encodeBase32LowerCaseNoPadding, encodeHexLowerCase } from '@oslojs/encoding'; +import { encodeHexLowerCase } from '@oslojs/encoding'; import { sha256 } from '@oslojs/crypto/sha2'; import { userTable, type User } from './schema/user'; import { sessionTable, type Session } from './schema/session'; -import { DB as db } from './connect'; import { env } from '$env/dynamic/private'; export const inactivityTimeout = Number.parseInt(env.INACTIVITY_TIMEOUT ?? '120') * 60 * 1000; export const maxActiveSessions = Number.parseInt(env.MAX_ACTIVE_SESSIONS ?? '3'); -export function generateSessionToken(): string { - const bytes = new Uint8Array(20); - crypto.getRandomValues(bytes); - const token = encodeBase32LowerCaseNoPadding(bytes); - return token; -} - export async function createSession( + db: Database, token: string, userId: number, building: string @@ -51,7 +45,10 @@ export async function createSession( } } -export async function validateSessionToken(token: string): Promise { +export async function validateSessionToken( + db: Database, + token: string +): Promise { // Assert that token is valid if (token === null || token === undefined || token === '') { throw new Error('Invalid token'); @@ -86,7 +83,7 @@ export async function validateSessionToken(token: string): Promise { +export async function invalidateSession(db: Database, sessionId: string): Promise { // Assert that sessionId is valid if (sessionId === null || sessionId === undefined || sessionId === '') { throw new Error('Invalid sessionId'); @@ -100,7 +97,7 @@ export async function invalidateSession(sessionId: string): Promise { } // Invalidate sessions that exceed the maximum number of sessions -export async function invalidateExcessSessions(userId: number): Promise { +export async function invalidateExcessSessions(db: Database, userId: number): Promise { // Assert that userId is valid if (userId === null || userId === undefined) { throw new Error('Invalid userId'); diff --git a/src/lib/server/db/user.ts b/src/lib/server/db/user.ts index b7df330..838d592 100644 --- a/src/lib/server/db/user.ts +++ b/src/lib/server/db/user.ts @@ -1,9 +1,9 @@ +import type { Database } from './connect'; import { and, desc, eq, gt } from 'drizzle-orm'; import { ratelimitTable, userTable } from './schema/user'; import { hashPassword, verifyPasswordStrength } from '../password'; -import { DB as db } from './connect'; -export async function createUser(username: string, password: string): Promise { +export async function createUser(db: Database, username: string, password: string): Promise { // Assert that username is valid if (username === undefined || username === null || username === '') { throw new Error('Invalid username'); @@ -32,6 +32,7 @@ export async function createUser(username: string, password: string): Promise { // Assert that username is valid @@ -61,6 +62,7 @@ export async function getUserIdAndPasswordHash( // Returns true if the user is ratelimited, otherwise false. export async function checkUserRatelimit( + db: Database, userId: number, ratelimitMaxAttempts: number, ratelimitTimeout: number diff --git a/src/lib/server/session.ts b/src/lib/server/session.ts index 33d672d..8ca141f 100644 --- a/src/lib/server/session.ts +++ b/src/lib/server/session.ts @@ -1,5 +1,13 @@ import type { RequestEvent } from '@sveltejs/kit'; import { inactivityTimeout } from './db/session'; +import { encodeBase32LowerCaseNoPadding } from '@oslojs/encoding'; + +export function generateSessionToken(): string { + const bytes = new Uint8Array(20); + crypto.getRandomValues(bytes); + const token = encodeBase32LowerCaseNoPadding(bytes); + return token; +} export function setSessionTokenCookie(event: RequestEvent, token: string, timestamp: Date): void { event.cookies.set('session', token, { diff --git a/src/lib/utils/search.ts b/src/lib/utils/search.ts deleted file mode 100644 index b8e717d..0000000 --- a/src/lib/utils/search.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { getPersons } from '$lib/server/db/person'; -import type { Person } from '$lib/types/person'; - -export async function search(query?: string): Promise { - return await getPersons(1000, 0, query); -} diff --git a/src/routes/(dashboard)/+page.server.ts b/src/routes/(dashboard)/+page.server.ts index 5a573b9..c2f4a8d 100644 --- a/src/routes/(dashboard)/+page.server.ts +++ b/src/routes/(dashboard)/+page.server.ts @@ -1,13 +1,13 @@ -import { togglePersonState } from '$lib/server/db/person'; +import { getPersons, togglePersonState } from '$lib/server/db/person'; import { fail, redirect, type Actions } from '@sveltejs/kit'; import type { PageServerLoad } from './$types'; import { invalidateSession } from '$lib/server/db/session'; import { deleteSessionTokenCookie } from '$lib/server/session'; -import { search } from '$lib/utils/search'; -export const load: PageServerLoad = async () => { +export const load: PageServerLoad = async ({ locals }) => { + const { database } = locals; try { - const persons = await search(); + const persons = await getPersons(database, 1000, 0); return { persons }; @@ -20,7 +20,8 @@ export const load: PageServerLoad = async () => { }; export const actions: Actions = { - search: async ({ request }) => { + search: async ({ locals, request }) => { + const { database } = locals; try { const formData = await request.formData(); const searchQuery = formData.get('q'); @@ -32,7 +33,7 @@ export const actions: Actions = { }); } - const persons = await search(searchQuery); + const persons = await getPersons(database, 1000, 0, searchQuery); return { searchQuery, persons @@ -44,7 +45,8 @@ export const actions: Actions = { }); } }, - togglestate: async ({ request, locals }) => { + togglestate: async ({ locals, request }) => { + const { database } = locals; try { const formData = await request.formData(); const idS = formData.get('id'); @@ -79,9 +81,9 @@ export const actions: Actions = { const { username } = locals.user; const id = Number.parseInt(idS); - await togglePersonState(id, building, username); + await togglePersonState(database, id, building, username); - const persons = await search(searchQuery); + const persons = await getPersons(database, 1000, 0, searchQuery); return { searchQuery, persons, @@ -95,8 +97,10 @@ export const actions: Actions = { } }, logout: async (event) => { + const { locals, request } = event; + const { database } = locals; try { - const formData = await event.request.formData(); + const formData = await request.formData(); const idS = formData.get('id_session'); if (idS === null || idS === undefined || typeof idS !== 'string' || idS === '') { return fail(400, { @@ -104,7 +108,7 @@ export const actions: Actions = { }); } - await invalidateSession(idS); + await invalidateSession(database, idS); deleteSessionTokenCookie(event); } catch (err: unknown) { console.debug(`Failed to logout: ${(err as Error).message}`); diff --git a/src/routes/(dashboard)/create/employee/+page.server.ts b/src/routes/(dashboard)/create/employee/+page.server.ts index 73d8353..aa7f8b3 100644 --- a/src/routes/(dashboard)/create/employee/+page.server.ts +++ b/src/routes/(dashboard)/create/employee/+page.server.ts @@ -9,9 +9,10 @@ import { formSchema } from './schema'; import type { PageServerLoad } from './$types'; import { Employee } from '$lib/types/person'; -export const load: PageServerLoad = async () => { +export const load: PageServerLoad = async ({ locals }) => { + const { database } = locals; const form = await superValidate(zod(formSchema)); - const departments = await getDepartments(); + const departments = await getDepartments(database); return { form, @@ -20,7 +21,8 @@ export const load: PageServerLoad = async () => { }; export const actions: Actions = { - default: async ({ request, locals }) => { + default: async ({ locals, request }) => { + const { database } = locals; const form = await superValidate(request, zod(formSchema)); if (!form.valid) { return fail(400, { @@ -41,7 +43,7 @@ export const actions: Actions = { const type = Employee; const building = locals.session.building; const creator = locals.user.username; - await createPerson(identifier, type, fname, lname, department, building, creator); + await createPerson(database, identifier, type, fname, lname, department, building, creator); } catch (err: unknown) { console.debug(`Failed to create employee: ${(err as Error).message}`); return fail(400, { diff --git a/src/routes/(dashboard)/create/student/+page.server.ts b/src/routes/(dashboard)/create/student/+page.server.ts index bcfe01d..9f559d5 100644 --- a/src/routes/(dashboard)/create/student/+page.server.ts +++ b/src/routes/(dashboard)/create/student/+page.server.ts @@ -9,9 +9,10 @@ import { formSchema } from './schema'; import type { PageServerLoad } from './$types'; import { Student } from '$lib/types/person'; -export const load: PageServerLoad = async () => { +export const load: PageServerLoad = async ({ locals }) => { + const { database } = locals; const form = await superValidate(zod(formSchema)); - const departments = await getDepartments(); + const departments = await getDepartments(database); return { form, @@ -20,7 +21,8 @@ export const load: PageServerLoad = async () => { }; export const actions: Actions = { - default: async ({ request, locals }) => { + default: async ({ locals, request }) => { + const { database } = locals; const form = await superValidate(request, zod(formSchema)); if (!form.valid) { return fail(400, { @@ -41,7 +43,7 @@ export const actions: Actions = { const type = Student; const building = locals.session.building; const creator = locals.user.username; - await createPerson(identifier, type, fname, lname, department, building, creator); + await createPerson(database, identifier, type, fname, lname, department, building, creator); } catch (err: unknown) { console.debug(`Failed to create student: ${(err as Error).message}`); return fail(400, { diff --git a/src/routes/(dashboard)/statistics/+page.server.ts b/src/routes/(dashboard)/statistics/+page.server.ts index 2446f29..b9f9cee 100644 --- a/src/routes/(dashboard)/statistics/+page.server.ts +++ b/src/routes/(dashboard)/statistics/+page.server.ts @@ -2,10 +2,11 @@ import { getPersonsCountPerBuilding, getPersonsCountPerDepartment } from '$lib/s import { fail, type Actions } from '@sveltejs/kit'; import type { PageServerLoad } from './$types'; -export const load: PageServerLoad = async () => { +export const load: PageServerLoad = async ({ locals }) => { + const { database } = locals; try { - const personsCountP = getPersonsCountPerDepartment(); - const personsInsideCountP = getPersonsCountPerBuilding(); + const personsCountP = getPersonsCountPerDepartment(database); + const personsInsideCountP = getPersonsCountPerBuilding(database); const personsCount = await personsCountP; const personsInsideCount = await personsInsideCountP; @@ -23,10 +24,11 @@ export const load: PageServerLoad = async () => { }; export const actions: Actions = { - default: async () => { + default: async ({ locals }) => { + const { database } = locals; try { - const personsCountP = getPersonsCountPerDepartment(); - const personsInsideCountP = getPersonsCountPerBuilding(); + const personsCountP = getPersonsCountPerDepartment(database); + const personsInsideCountP = getPersonsCountPerBuilding(database); const personsCount = await personsCountP; const personsInsideCount = await personsInsideCountP; diff --git a/src/routes/admin/create/building/+page.server.ts b/src/routes/admin/create/building/+page.server.ts index d569701..49bfddd 100644 --- a/src/routes/admin/create/building/+page.server.ts +++ b/src/routes/admin/create/building/+page.server.ts @@ -15,8 +15,9 @@ export const load: PageServerLoad = async () => { }; export const actions: Actions = { - default: async (event) => { - const form = await superValidate(event.request, zod(formSchema)); + default: async ({ locals, request }) => { + const { database } = locals; + const form = await superValidate(request, zod(formSchema)); if (!form.valid) { return fail(400, { form, @@ -35,7 +36,7 @@ export const actions: Actions = { try { const { building } = form.data; - await createBuilding(building); + await createBuilding(database, building); } catch (err: unknown) { console.debug(`Failed to create building: ${(err as Error).message}`); return fail(400, { diff --git a/src/routes/admin/create/department/+page.server.ts b/src/routes/admin/create/department/+page.server.ts index 623b0cf..f9b688c 100644 --- a/src/routes/admin/create/department/+page.server.ts +++ b/src/routes/admin/create/department/+page.server.ts @@ -15,8 +15,9 @@ export const load: PageServerLoad = async () => { }; export const actions: Actions = { - default: async (event) => { - const form = await superValidate(event.request, zod(formSchema)); + default: async ({ locals, request }) => { + const { database } = locals; + const form = await superValidate(request, zod(formSchema)); if (!form.valid) { return fail(400, { form, @@ -35,7 +36,7 @@ export const actions: Actions = { try { const { department } = form.data; - await createDepartment(department); + await createDepartment(database, department); } catch (err: unknown) { console.debug(`Failed to create department: ${(err as Error).message}`); return fail(400, { diff --git a/src/routes/admin/nuke/+page.server.ts b/src/routes/admin/nuke/+page.server.ts index 7de4fc4..0c3118f 100644 --- a/src/routes/admin/nuke/+page.server.ts +++ b/src/routes/admin/nuke/+page.server.ts @@ -7,10 +7,11 @@ import { getBuildings } from '$lib/server/db/building'; import { validateSecret } from '$lib/server/secret'; import { getPersonTypes, removePersonsFromBuilding } from '$lib/server/db/person'; -export const load: PageServerLoad = async () => { +export const load: PageServerLoad = async ({ locals }) => { + const { database } = locals; const form = await superValidate(zod(formSchema)); - const buildingsP = getBuildings(); - const personTypesP = getPersonTypes(); + const buildingsP = getBuildings(database); + const personTypesP = getPersonTypes(database); const buildings = await buildingsP; const personTypes = await personTypesP; @@ -22,8 +23,9 @@ export const load: PageServerLoad = async () => { }; export const actions: Actions = { - default: async (event) => { - const form = await superValidate(event.request, zod(formSchema)); + default: async ({ locals, request }) => { + const { database } = locals; + const form = await superValidate(request, zod(formSchema)); if (!form.valid) { return fail(400, { form, @@ -42,7 +44,7 @@ export const actions: Actions = { try { const { building, personType } = form.data; - await removePersonsFromBuilding(building, personType); + await removePersonsFromBuilding(database, building, personType); } catch (err: unknown) { console.debug(`Failed to nuke building: ${(err as Error).message}`); return fail(400, { diff --git a/src/routes/admin/register/+page.server.ts b/src/routes/admin/register/+page.server.ts index 62019a8..d63bb66 100644 --- a/src/routes/admin/register/+page.server.ts +++ b/src/routes/admin/register/+page.server.ts @@ -16,7 +16,9 @@ export const load: PageServerLoad = async () => { export const actions: Actions = { default: async (event) => { - const form = await superValidate(event.request, zod(formSchema)); + const { locals, request } = event; + const { database } = locals; + const form = await superValidate(request, zod(formSchema)); if (!form.valid) { return fail(400, { form, @@ -36,7 +38,7 @@ export const actions: Actions = { try { const { username, password } = form.data; // Create the new user - await createUser(username, password); + await createUser(database, username, password); } catch (err: unknown) { console.debug(`Failed to register: ${(err as Error).message}`); return fail(400, { diff --git a/src/routes/login/+page.server.ts b/src/routes/login/+page.server.ts index 9360d19..6e9a6ec 100644 --- a/src/routes/login/+page.server.ts +++ b/src/routes/login/+page.server.ts @@ -1,11 +1,7 @@ -import { - createSession, - generateSessionToken, - invalidateExcessSessions -} from '$lib/server/db/session'; +import { createSession, invalidateExcessSessions } from '$lib/server/db/session'; import { checkUserRatelimit, getUserIdAndPasswordHash } from '$lib/server/db/user'; import { verifyPasswordHash } from '$lib/server/password'; -import { setSessionTokenCookie } from '$lib/server/session'; +import { generateSessionToken, setSessionTokenCookie } from '$lib/server/session'; import { fail, redirect, type Actions } from '@sveltejs/kit'; import { superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; @@ -14,9 +10,10 @@ import type { PageServerLoad } from './$types'; import { getBuildings } from '$lib/server/db/building'; import { env } from '$env/dynamic/private'; -export const load: PageServerLoad = async () => { +export const load: PageServerLoad = async ({ locals }) => { + const { database } = locals; const form = await superValidate(zod(formSchema)); - const buildings = await getBuildings(); + const buildings = await getBuildings(database); return { form, @@ -26,10 +23,12 @@ export const load: PageServerLoad = async () => { export const actions: Actions = { default: async (event) => { + const { locals, request } = event; + const { database } = locals; const ratelimitMaxAttempts = Number.parseInt(env.RATELIMIT_MAX_ATTEMPTS ?? '5'); const ratelimitTimeout = Number.parseInt(env.RATELIMIT_TIMEOUT ?? '60'); - const form = await superValidate(event.request, zod(formSchema)); + const form = await superValidate(request, zod(formSchema)); if (!form.valid) { return fail(400, { form, @@ -41,10 +40,15 @@ export const actions: Actions = { const { username, password, building } = form.data; // Check if the username exists - const { id, passwordHash } = await getUserIdAndPasswordHash(username); + const { id, passwordHash } = await getUserIdAndPasswordHash(database, username); // Check if the ratelimit has been hit - const ratelimited = await checkUserRatelimit(id, ratelimitMaxAttempts, ratelimitTimeout); + const ratelimited = await checkUserRatelimit( + database, + id, + ratelimitMaxAttempts, + ratelimitTimeout + ); if (ratelimited) { console.warn(`Ratelimited by IP: ${event.getClientAddress()}`); return fail(401, { @@ -61,11 +65,11 @@ export const actions: Actions = { // Create a new session token const sessionToken = generateSessionToken(); - const session = await createSession(sessionToken, id, building); + const session = await createSession(database, sessionToken, id, building); setSessionTokenCookie(event, sessionToken, session.timestamp); // Invalidate sessions that exceed the maximum number of sessions - await invalidateExcessSessions(id); + await invalidateExcessSessions(database, id); } catch (err: unknown) { console.debug(`Failed to login: ${(err as Error).message}`); return fail(401, {