diff --git a/package.json b/package.json index 3eddb38..c3e1746 100644 --- a/package.json +++ b/package.json @@ -14,10 +14,7 @@ "lint": "prettier --check . && eslint .", "test:unit": "vitest", "test": "npm run test:unit -- --run && npm run test:e2e", - "test:e2e": "playwright test", - "db:push": "drizzle-kit push", - "db:migrate": "drizzle-kit migrate", - "db:studio": "drizzle-kit studio" + "test:e2e": "playwright test" }, "devDependencies": { "@eslint/compat": "^1.2.5", @@ -31,7 +28,6 @@ "autoprefixer": "^10.4.20", "bits-ui": "^0.22.0", "clsx": "^2.1.1", - "drizzle-kit": "^0.30.2", "eslint": "^9.18.0", "eslint-config-prettier": "^10.0.1", "eslint-plugin-svelte": "^2.46.1", @@ -56,13 +52,11 @@ }, "dependencies": { "@inlang/paraglide-sveltekit": "0.15.5", - "@libsql/client": "^0.14.0", "@node-rs/argon2": "^2.0.2", "@oslojs/crypto": "^1.0.1", "@oslojs/encoding": "^1.1.0", "@tauri-apps/api": "^2.2.0", "dexie": "^4.0.11", - "drizzle-orm": "^0.38.4", "svelte-radix": "^2.0.1" } } diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 231c739..581d437 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,28 +1,7 @@ import { sequence } from '@sveltejs/kit/hooks'; -import * as auth from '$lib/server/auth.js'; import type { Handle } from '@sveltejs/kit'; import { i18n } from '$lib/i18n'; const handleParaglide: Handle = i18n.handle(); -const handleAuth: Handle = async ({ event, resolve }) => { - const sessionToken = event.cookies.get(auth.sessionCookieName); - if (!sessionToken) { - event.locals.user = null; - event.locals.session = null; - return resolve(event); - } - const { session, user } = await auth.validateSessionToken(sessionToken); - if (session) { - auth.setSessionTokenCookie(event, sessionToken, session.expiresAt); - } else { - auth.deleteSessionTokenCookie(event); - } - - event.locals.user = user; - event.locals.session = session; - - return resolve(event); -}; - -export const handle: Handle = sequence(handleParaglide, handleAuth); +export const handle: Handle = sequence(handleParaglide); diff --git a/src/lib/server/auth.ts b/src/lib/server/auth.ts deleted file mode 100644 index 38c9930..0000000 --- a/src/lib/server/auth.ts +++ /dev/null @@ -1,81 +0,0 @@ -import type { RequestEvent } from '@sveltejs/kit'; -import { eq } from 'drizzle-orm'; -import { sha256 } from '@oslojs/crypto/sha2'; -import { encodeBase64url, encodeHexLowerCase } from '@oslojs/encoding'; -import { db } from '$lib/server/db'; -import * as table from '$lib/server/db/schema'; - -const DAY_IN_MS = 1000 * 60 * 60 * 24; - -export const sessionCookieName = 'auth-session'; - -export function generateSessionToken() { - const bytes = crypto.getRandomValues(new Uint8Array(18)); - const token = encodeBase64url(bytes); - return token; -} - -export async function createSession(token: string, userId: string) { - const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token))); - const session: table.Session = { - id: sessionId, - userId, - expiresAt: new Date(Date.now() + DAY_IN_MS * 30) - }; - await db.insert(table.session).values(session); - return session; -} - -export async function validateSessionToken(token: string) { - const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token))); - const [result] = await db - .select({ - // Adjust user table here to tweak returned data - user: { id: table.user.id, username: table.user.username }, - session: table.session - }) - .from(table.session) - .innerJoin(table.user, eq(table.session.userId, table.user.id)) - .where(eq(table.session.id, sessionId)); - - if (!result) { - return { session: null, user: null }; - } - const { session, user } = result; - - const sessionExpired = Date.now() >= session.expiresAt.getTime(); - if (sessionExpired) { - await db.delete(table.session).where(eq(table.session.id, session.id)); - return { session: null, user: null }; - } - - const renewSession = Date.now() >= session.expiresAt.getTime() - DAY_IN_MS * 15; - if (renewSession) { - session.expiresAt = new Date(Date.now() + DAY_IN_MS * 30); - await db - .update(table.session) - .set({ expiresAt: session.expiresAt }) - .where(eq(table.session.id, session.id)); - } - - return { session, user }; -} - -export type SessionValidationResult = Awaited>; - -export async function invalidateSession(sessionId: string) { - await db.delete(table.session).where(eq(table.session.id, sessionId)); -} - -export function setSessionTokenCookie(event: RequestEvent, token: string, expiresAt: Date) { - event.cookies.set(sessionCookieName, token, { - expires: expiresAt, - path: '/' - }); -} - -export function deleteSessionTokenCookie(event: RequestEvent) { - event.cookies.delete(sessionCookieName, { - path: '/' - }); -} diff --git a/src/lib/server/db/index.ts b/src/lib/server/db/index.ts deleted file mode 100644 index 2ab8f52..0000000 --- a/src/lib/server/db/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { drizzle } from 'drizzle-orm/libsql'; -import { createClient } from '@libsql/client'; -import { env } from '$env/dynamic/private'; -if (!env.DATABASE_URL) throw new Error('DATABASE_URL is not set'); -const client = createClient({ url: env.DATABASE_URL }); -export const db = drizzle(client); diff --git a/src/lib/server/db/schema.ts b/src/lib/server/db/schema.ts deleted file mode 100644 index 166d477..0000000 --- a/src/lib/server/db/schema.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'; - -export const user = sqliteTable('user', { - id: text('id').primaryKey(), - age: integer('age'), - username: text('username').notNull().unique(), - passwordHash: text('password_hash').notNull() -}); - -export const session = sqliteTable('session', { - id: text('id').primaryKey(), - userId: text('user_id') - .notNull() - .references(() => user.id), - expiresAt: integer('expires_at', { mode: 'timestamp' }).notNull() -}); - -export type Session = typeof session.$inferSelect; - -export type User = typeof user.$inferSelect; diff --git a/src/routes/demo/+page.svelte b/src/routes/demo/+page.svelte deleted file mode 100644 index 943fadf..0000000 --- a/src/routes/demo/+page.svelte +++ /dev/null @@ -1,2 +0,0 @@ -paraglide -lucia diff --git a/src/routes/demo/lucia/+page.server.ts b/src/routes/demo/lucia/+page.server.ts deleted file mode 100644 index c6b3d98..0000000 --- a/src/routes/demo/lucia/+page.server.ts +++ /dev/null @@ -1,22 +0,0 @@ -import * as auth from '$lib/server/auth'; -import { fail, redirect } from '@sveltejs/kit'; -import type { Actions, PageServerLoad } from './$types'; - -export const load: PageServerLoad = async (event) => { - if (!event.locals.user) { - return redirect(302, '/demo/lucia/login'); - } - return { user: event.locals.user }; -}; - -export const actions: Actions = { - logout: async (event) => { - if (!event.locals.session) { - return fail(401); - } - await auth.invalidateSession(event.locals.session.id); - auth.deleteSessionTokenCookie(event); - - return redirect(302, '/demo/lucia/login'); - } -}; diff --git a/src/routes/demo/lucia/+page.svelte b/src/routes/demo/lucia/+page.svelte deleted file mode 100644 index cefb2d1..0000000 --- a/src/routes/demo/lucia/+page.svelte +++ /dev/null @@ -1,12 +0,0 @@ - - -

Hi, {data.user.username}!

-

Your user ID is {data.user.id}.

-
- -
diff --git a/src/routes/demo/lucia/login/+page.server.ts b/src/routes/demo/lucia/login/+page.server.ts deleted file mode 100644 index bd3e9cc..0000000 --- a/src/routes/demo/lucia/login/+page.server.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { hash, verify } from '@node-rs/argon2'; -import { encodeBase32LowerCase } from '@oslojs/encoding'; -import { fail, redirect } from '@sveltejs/kit'; -import { eq } from 'drizzle-orm'; -import * as auth from '$lib/server/auth'; -import { db } from '$lib/server/db'; -import * as table from '$lib/server/db/schema'; -import type { Actions, PageServerLoad } from './$types'; - -export const load: PageServerLoad = async (event) => { - if (event.locals.user) { - return redirect(302, '/demo/lucia'); - } - return {}; -}; - -export const actions: Actions = { - login: async (event) => { - const formData = await event.request.formData(); - const username = formData.get('username'); - const password = formData.get('password'); - - if (!validateUsername(username)) { - return fail(400, { - message: 'Invalid username (min 3, max 31 characters, alphanumeric only)' - }); - } - if (!validatePassword(password)) { - return fail(400, { message: 'Invalid password (min 6, max 255 characters)' }); - } - - const results = await db.select().from(table.user).where(eq(table.user.username, username)); - - const existingUser = results.at(0); - if (!existingUser) { - return fail(400, { message: 'Incorrect username or password' }); - } - - const validPassword = await verify(existingUser.passwordHash, password, { - memoryCost: 19456, - timeCost: 2, - outputLen: 32, - parallelism: 1 - }); - if (!validPassword) { - return fail(400, { message: 'Incorrect username or password' }); - } - - const sessionToken = auth.generateSessionToken(); - const session = await auth.createSession(sessionToken, existingUser.id); - auth.setSessionTokenCookie(event, sessionToken, session.expiresAt); - - return redirect(302, '/demo/lucia'); - }, - register: async (event) => { - const formData = await event.request.formData(); - const username = formData.get('username'); - const password = formData.get('password'); - - if (!validateUsername(username)) { - return fail(400, { message: 'Invalid username' }); - } - if (!validatePassword(password)) { - return fail(400, { message: 'Invalid password' }); - } - - const userId = generateUserId(); - const passwordHash = await hash(password, { - // recommended minimum parameters - memoryCost: 19456, - timeCost: 2, - outputLen: 32, - parallelism: 1 - }); - - try { - await db.insert(table.user).values({ id: userId, username, passwordHash }); - - const sessionToken = auth.generateSessionToken(); - const session = await auth.createSession(sessionToken, userId); - auth.setSessionTokenCookie(event, sessionToken, session.expiresAt); - } catch (e) { - return fail(500, { message: 'An error has occurred' }); - } - return redirect(302, '/demo/lucia'); - } -}; - -function generateUserId() { - // ID with 120 bits of entropy, or about the same as UUID v4. - const bytes = crypto.getRandomValues(new Uint8Array(15)); - const id = encodeBase32LowerCase(bytes); - return id; -} - -function validateUsername(username: unknown): username is string { - return ( - typeof username === 'string' && - username.length >= 3 && - username.length <= 31 && - /^[a-z0-9_-]+$/.test(username) - ); -} - -function validatePassword(password: unknown): password is string { - return typeof password === 'string' && password.length >= 6 && password.length <= 255; -} diff --git a/src/routes/demo/lucia/login/+page.svelte b/src/routes/demo/lucia/login/+page.svelte deleted file mode 100644 index a3138d7..0000000 --- a/src/routes/demo/lucia/login/+page.svelte +++ /dev/null @@ -1,21 +0,0 @@ - - -

Login/Register

-
- - - - -
-

{form?.message ?? ''}

diff --git a/src/routes/demo/paraglide/+page.svelte b/src/routes/demo/paraglide/+page.svelte deleted file mode 100644 index 112810a..0000000 --- a/src/routes/demo/paraglide/+page.svelte +++ /dev/null @@ -1,19 +0,0 @@ - - -

{m.hello_world({ name: 'SvelteKit User' })}

-
- - -