added login with pages and database, also correct errors in score submit

This commit is contained in:
2026-06-22 13:36:34 +01:00
parent 192564fb79
commit fbc181c890
42 changed files with 31351 additions and 1599 deletions

73
src/lib/server/auth.ts Normal file
View File

@@ -0,0 +1,73 @@
// src/lib/server/auth.ts
import { sha256 } from '@oslojs/crypto/sha2';
import { encodeBase32LowerCaseNoPadding, encodeHexLowerCase } from '@oslojs/encoding';
import { eq } from 'drizzle-orm';
import { db } from '$lib/server/db';
import { sessions, scorers as scorers } from '$lib/server/db/schema';
import type { RequestEvent } from '@sveltejs/kit';
const DAY_MS = 1000 * 60 * 60 * 24;
const SESSION_COOKIE = 'session';
export function generateSessionToken(): string {
const bytes = new Uint8Array(20);
crypto.getRandomValues(bytes);
return encodeBase32LowerCaseNoPadding(bytes);
}
export async function createSession(token: string, userId: string) {
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
const expiresAt = new Date(Date.now() + DAY_MS * 30);
await db.insert(sessions).values({ id: sessionId, userId, expiresAt });
return { id: sessionId, userId, expiresAt };
}
export async function validateSessionToken(token: string) {
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
const [row] = await db
.select({ session: sessions, user: scorers })
.from(sessions)
.innerJoin(scorers, eq(sessions.userId, scorers.id))
.where(eq(sessions.id, sessionId));
if (!row) return { session: null, user: null };
// Expired — clean up and reject
if (Date.now() >= row.session.expiresAt.getTime()) {
await db.delete(sessions).where(eq(sessions.id, sessionId));
return { session: null, user: null };
}
// Sliding expiration: renew if past the halfway mark
if (Date.now() >= row.session.expiresAt.getTime() - DAY_MS * 15) {
const newExpiresAt = new Date(Date.now() + DAY_MS * 30);
await db.update(sessions).set({ expiresAt: newExpiresAt }).where(eq(sessions.id, sessionId));
row.session.expiresAt = newExpiresAt;
}
return { session: row.session, user: { id: row.user.id, username: row.user.username } };
}
export async function invalidateSession(sessionId: string) {
await db.delete(sessions).where(eq(sessions.id, sessionId));
}
export function setSessionTokenCookie(event: RequestEvent, token: string, expiresAt: Date) {
event.cookies.set(SESSION_COOKIE, token, {
expires: expiresAt,
path: '/',
httpOnly: true,
secure: !import.meta.env.DEV, // allow http in local dev
sameSite: 'lax'
});
}
export function deleteSessionTokenCookie(event: RequestEvent) {
event.cookies.delete(SESSION_COOKIE, { path: '/' });
}
export function getSessionToken(event: RequestEvent) {
return event.cookies.get(SESSION_COOKIE) ?? null;
}

View File

@@ -1,6 +1,21 @@
import { sql, eq } from 'drizzle-orm';
import { integer, sqliteTable, text, sqliteView } from 'drizzle-orm/sqlite-core';
export const scorers = sqliteTable('users', {
id: text('id').primaryKey(),
username: text('username').notNull().unique(),
role: text('scorer_role').notNull().default('scorer'),
passwordHash: text('password_hash').notNull()
});
export const sessions = sqliteTable('sessions', {
id: text('id').primaryKey(), // SHA-256 hash of the token, never the raw token
userId: text('user_id')
.notNull()
.references(() => scorers.id),
expiresAt: integer('expires_at', { mode: 'timestamp' }).notNull()
});
export const players = sqliteTable('players', {
id: integer('players_id').primaryKey({ autoIncrement: true }),
firstName: text('firstName').notNull(),