From 4214ac2f154e10f87b8c2f02195e952d4952a22f Mon Sep 17 00:00:00 2001 From: Niki Wix Skaarup Date: Mon, 16 Jun 2025 21:25:07 +0200 Subject: [PATCH] prepared queries --- bun.lock | 26 +++++------ package.json | 4 +- src/server/auth.ts | 109 ++++++++++++++++++++++++++++++--------------- 3 files changed, 87 insertions(+), 52 deletions(-) diff --git a/bun.lock b/bun.lock index 00a2b0e..461f56f 100644 --- a/bun.lock +++ b/bun.lock @@ -8,7 +8,7 @@ "bun-plugin-tailwind": "^0.0.15", "drizzle-orm": "^0.42.0", "jose": "^6.0.11", - "svelte": "^5.33.13", + "svelte": "^5.33.14", "tailwindcss": "^4.1.8", }, "devDependencies": { @@ -93,21 +93,21 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="], - "@oxlint/darwin-arm64": ["@oxlint/darwin-arm64@0.18.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-lJanO+V1JcV4QpfkFo/4L8qyjoL4jqSrOgA4bsf2JK0QH1w+bpQ4979dDF0HXsp1uJoyYVtkVuT8wP4C9TdJzg=="], + "@oxlint/darwin-arm64": ["@oxlint/darwin-arm64@1.0.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Ei8wLh65Th/si5EY6mfQIXVpdXbJWOoh56FaxxPgVxTeJaj3NHUIlxICHkvTZ5dz8bnOFcbS/+9MaW8Qkzfm9g=="], - "@oxlint/darwin-x64": ["@oxlint/darwin-x64@0.18.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-UiGJh67KSoS2RyEQQCjp5qKHBcu1vk/5kaF4f9db4JJmatpyEuqDjuvE6GBqhqWAKh064IvSVubQtvRp0v1M6w=="], + "@oxlint/darwin-x64": ["@oxlint/darwin-x64@1.0.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-dbdtQ+rJTUb4jFKTzV+j08yYcR8lZssLF10n7MggK/jI7pBtoQN04cupzYdkxOWSy6uDXjDmWYFDIqlTqV7zOg=="], - "@oxlint/linux-arm64-gnu": ["@oxlint/linux-arm64-gnu@0.18.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-3CsWnXoAB26GxlQzPiJKzd6lY0Z/S3Or6oAF/AwbS+Bn2hiB73Kt+BFOcFp5m3EOEDdGYHfRwIn2y1wUf76diA=="], + "@oxlint/linux-arm64-gnu": ["@oxlint/linux-arm64-gnu@1.0.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-71wy9zMxsAeRhCFQjUkDLT8N5tm10L5FxNxsUcEsezgM187X9tPGP1gwlFpYig7F+bg2X1dijFuTA/FSe0YpKg=="], - "@oxlint/linux-arm64-musl": ["@oxlint/linux-arm64-musl@0.18.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-gzVgf1GT+WHsujlj4MtQUvzILJ+SlYPib+xLx0aFIHE5jrnd6ZUuT+Nxrnn/cLA+f+9cPmDD7JByVksPFHhT2w=="], + "@oxlint/linux-arm64-musl": ["@oxlint/linux-arm64-musl@1.0.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UbD4+2k7aGZOFtKK/yeESX7Fv0w9gQbcjrjr1HGY7QOYg7XlFlqzycZdPS6XbAuKA5oOXFpafaYOD4AyX3p2AA=="], - "@oxlint/linux-x64-gnu": ["@oxlint/linux-x64-gnu@0.18.0", "", { "os": "linux", "cpu": "x64" }, "sha512-D94AgHk4qZt2v658OXZP13Re/Q1/hRQhMdOy+pzeih9POcTY31NOHmBInyI2OjZzUMT3JzUKc+efAmX7rlDJFg=="], + "@oxlint/linux-x64-gnu": ["@oxlint/linux-x64-gnu@1.0.0", "", { "os": "linux", "cpu": "x64" }, "sha512-0NXWqsm65I3VaLgADW4y9r7Pwurqgs2fr1lqoTyTIlidD18LQ3UMAWp8NzBPMCYzw8c/rTgOzsFf0gLtxzMtwg=="], - "@oxlint/linux-x64-musl": ["@oxlint/linux-x64-musl@0.18.0", "", { "os": "linux", "cpu": "x64" }, "sha512-B3kGrEE68F7qvyVaH2ihYB00E/iQOsi2SN+NeDht9FyUiftZRtIYlvZ/VE8l8yxXX/TFtwhed3bg2/IvaJ8Tpg=="], + "@oxlint/linux-x64-musl": ["@oxlint/linux-x64-musl@1.0.0", "", { "os": "linux", "cpu": "x64" }, "sha512-AY1NLnVQI+tBeuaB8KCriWfiD6O1zZFAQHphRDcZiqSz4mauNq9FFuffW0N9RSR9hYttGr0UVdQ6eK72RhzOYg=="], - "@oxlint/win32-arm64": ["@oxlint/win32-arm64@0.18.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-d+q8Vkhy4va7UZAETov1BWDA5G9XItY6HR7j57Uso5xfqVmFvnk2+L0LH9Z/PPFwNNR/37vBB364bJ4V6v/DlQ=="], + "@oxlint/win32-arm64": ["@oxlint/win32-arm64@1.0.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-X9y2KAdoqT/jy/sITGDZNMJHJAmhDhofItBnCf2DWS1HPakdtCAKGX9KMx6SivTbtPn1+JpZgfHn4Y7rNMvujQ=="], - "@oxlint/win32-x64": ["@oxlint/win32-x64@0.18.0", "", { "os": "win32", "cpu": "x64" }, "sha512-gMxC4XrhnR5XRtjqhIvkPI1yyinwkSjp7VgeOTf4mDk0xwYROwg/mSWnKfDn7tXUKoWQLDsSfAp/SS8uSAQ9Bg=="], + "@oxlint/win32-x64": ["@oxlint/win32-x64@1.0.0", "", { "os": "win32", "cpu": "x64" }, "sha512-x2eQwZCfRUi6GG0lhRuC54O6TK2uW7UbIvERh83vPi0ftd+rtGUuJauNdyC+pPx+iwFToFVet43/5MBMu4bMWg=="], "@prettier/cli": ["@prettier/cli@0.7.1", "", { "dependencies": { "atomically": "^2.0.3", "fast-ignore": "^1.1.3", "find-up-json": "^2.0.4", "function-once": "^3.0.0", "import-meta-resolve": "^4.1.0", "is-binary-path": "^2.1.0", "js-yaml": "^4.1.0", "json-sorted-stringify": "^1.0.0", "json5": "^2.2.3", "kasi": "^1.1.0", "lomemo": "^1.0.0", "pioppo": "^1.2.0", "promise-resolve-timeout": "^2.0.0", "smol-toml": "^1.3.1", "specialist": "^1.4.5", "tiny-editorconfig": "^1.0.0", "tiny-jsonc": "^1.0.1", "tiny-readdir": "^2.7.4", "tiny-readdir-glob": "^1.23.1", "tiny-spinner": "^2.0.4", "worktank": "^2.7.3", "zeptomatch": "^2.0.0", "zeptomatch-escape": "^1.0.0", "zeptomatch-is-static": "^1.0.0" }, "peerDependencies": { "prettier": "^3.1.0 || ^4.0.0-alpha" }, "bin": { "prettier-next": "dist/bin.js" } }, "sha512-YoXPLOLmEEHP4MKgzcEilzaUtlo80Qm5Pb+59QbgDeOsIExGBkRqZmC2+iwzSwEhlhTEpGqDwqb3+nP/dPay9A=="], @@ -159,7 +159,7 @@ "esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="], - "esrap": ["esrap@1.4.6", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-F/D2mADJ9SHY3IwksD4DAXjTt7qt7GWUf3/8RhCNWmC/67tyb55dpimHmy7EplakFaflV0R/PC+fdSPqrRHAQw=="], + "esrap": ["esrap@1.4.9", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-3OMlcd0a03UGuZpPeUC1HxR3nA23l+HEyCiZw3b3FumJIN9KphoGzDJKMXI1S72jVS1dsenDyQC0kJlO1U9E1g=="], "fast-ignore": ["fast-ignore@1.1.3", "", { "dependencies": { "grammex": "^3.1.2", "string-escape-regex": "^1.0.0" } }, "sha512-xTo4UbrOKfEQgOFlPaqFScodTV/Wf3KATEqCZZSMh6OP4bcez0lTsqww3n3/Fve1q9u0jmfDP0q0nOhH4POZEg=="], @@ -207,7 +207,7 @@ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - "oxlint": ["oxlint@0.18.0", "", { "optionalDependencies": { "@oxlint/darwin-arm64": "0.18.0", "@oxlint/darwin-x64": "0.18.0", "@oxlint/linux-arm64-gnu": "0.18.0", "@oxlint/linux-arm64-musl": "0.18.0", "@oxlint/linux-x64-gnu": "0.18.0", "@oxlint/linux-x64-musl": "0.18.0", "@oxlint/win32-arm64": "0.18.0", "@oxlint/win32-x64": "0.18.0" }, "bin": { "oxlint": "bin/oxlint", "oxc_language_server": "bin/oxc_language_server" } }, "sha512-Dni48uC00HoBn63I1aFXS7ta7whJOMK5yBc/PQjQVQ1EWutHpcExhWO73XcOeljKUNsEkNWF/yTUFOIN6HbZiA=="], + "oxlint": ["oxlint@1.0.0", "", { "optionalDependencies": { "@oxlint/darwin-arm64": "1.0.0", "@oxlint/darwin-x64": "1.0.0", "@oxlint/linux-arm64-gnu": "1.0.0", "@oxlint/linux-arm64-musl": "1.0.0", "@oxlint/linux-x64-gnu": "1.0.0", "@oxlint/linux-x64-musl": "1.0.0", "@oxlint/win32-arm64": "1.0.0", "@oxlint/win32-x64": "1.0.0" }, "bin": { "oxlint": "bin/oxlint", "oxc_language_server": "bin/oxc_language_server" } }, "sha512-yyeryHnd21wPBLBEF4Uf8hvzJlftrIGHxyUaqFaP2JYiZ9cbiColygZhrezvv/Z/aThCmYu3j6iJMxlVPxNt6g=="], "pioppo": ["pioppo@1.2.1", "", { "dependencies": { "dettle": "^1.0.5", "when-exit": "^2.1.4" } }, "sha512-1oErGVWD6wFDPmrJWEY1Cj2p829UGT6Fw9OItYFxLkWtBjCvQSMC8wA5IcAR5ms/6gqiY8pnJvIV/+/Imyobew=="], @@ -239,9 +239,9 @@ "stubborn-fs": ["stubborn-fs@1.2.5", "", {}, "sha512-H2N9c26eXjzL/S/K+i/RHHcFanE74dptvvjM8iwzwbVcWY/zjBbgRqF3K0DY4+OD+uTTASTBvDoxPDaPN02D7g=="], - "svelte": ["svelte@5.33.14", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^1.4.6", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-kRlbhIlMTijbFmVDQFDeKXPLlX1/ovXwV0I162wRqQhRcygaqDIcu1d/Ese3H2uI+yt3uT8E7ndgDthQv5v5BA=="], + "svelte": ["svelte@5.33.19", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^1.4.8", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-udmgc1nnGeAgnfVJjOMfSOAqPAKv8N65MWN2kDuxo6BZthTaUcsLh4vP8bdZC0bMXLGn69smSZXgQOeuZBOn4Q=="], - "tailwindcss": ["tailwindcss@4.1.8", "", {}, "sha512-kjeW8gjdxasbmFKpVGrGd5T4i40mV5J2Rasw48QARfYeQ8YS9x02ON9SFWax3Qf616rt4Cp3nVNIj6Hd1mP3og=="], + "tailwindcss": ["tailwindcss@4.1.9", "", {}, "sha512-anBZRcvfNMsQdHB9XSGzAtIQWlhs49uK75jfkwrqjRUbjt4d7q9RE1wR1xWyfYZhLFnFX4ahWp88Au2lcEw5IQ=="], "tiny-bin": ["tiny-bin@1.11.1", "", { "dependencies": { "ansi-purge": "^1.0.1", "fast-string-width": "^1.1.0", "get-current-package": "^1.0.1", "tiny-colors": "^2.2.2", "tiny-levenshtein": "^1.0.1", "tiny-parse-argv": "^2.8.2", "tiny-updater": "^3.5.3" } }, "sha512-UFC5EwtmCkFshKOBgXZzNFJsHpZrtbWZ/jQj+pwoIGUUbmenlQGGVDOwVqVOuG1nTxICSd+GLp3b+j7dUKZr2Q=="], diff --git a/package.json b/package.json index 4202138..c8eabdd 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,8 @@ "bun-plugin-tailwind": "^0.0.15", "drizzle-orm": "^0.42.0", "jose": "^6.0.11", - "svelte": "^5.33.14", - "tailwindcss": "^4.1.8" + "svelte": "^5.33.19", + "tailwindcss": "^4.1.9" }, "peerDependencies": { "typescript": "^5.8.3" diff --git a/src/server/auth.ts b/src/server/auth.ts index 973a27f..a32076c 100644 --- a/src/server/auth.ts +++ b/src/server/auth.ts @@ -1,8 +1,8 @@ -import { env } from "bun"; -import { and, eq } from "drizzle-orm"; +import { env } from 'bun'; +import { and, eq, sql } from 'drizzle-orm'; import * as jose from 'jose'; -import { drizzleDB } from "./db"; -import { users, userSessions } from "./db/schema"; +import { drizzleDB } from './db'; +import { users, userSessions } from './db/schema'; if (!env.JWT_SECRET || env.JWT_SECRET.length < 16) { throw new Error('JWT_SECRET must be at least 16 characters long'); @@ -28,13 +28,26 @@ const cookieInit: Bun.CookieInit = { const renewalTime = Math.abs(Math.floor(maxAge / 7)); -type JWTPayload = { token: string, userId: string }; +type JWTPayload = { token: string; userId: string }; + +const getUserByEmail = drizzleDB + .select() + .from(users) + .where(eq(users.email, sql.placeholder('email'))) + .limit(1) + .prepare(); + +const insertSession = drizzleDB + .insert(userSessions) + .values({ + token: sql.placeholder('token'), + maxAge: sql.placeholder('maxAge'), + userId: sql.placeholder('userId'), + }) + .prepare(); async function login(email: string, password: string): Promise { - const result = await drizzleDB - .select() - .from(users) - .where(eq(users.email, email.toLocaleLowerCase())); + const result = await getUserByEmail.execute({ email: email.toLocaleLowerCase() }); if (result.length === 0) { return null; @@ -53,11 +66,7 @@ async function login(email: string, password: string): Promise { const token = Bun.randomUUIDv7('base64url'); - await drizzleDB.insert(userSessions).values({ - token, - maxAge: Date.now() + maxAge, - userId: user.id, - }); + await insertSession.execute({ token, maxAge: Date.now() + maxAge, userId: user.id }); const payload: JWTPayload = { token, userId: user.id }; @@ -71,7 +80,7 @@ async function login(email: string, password: string): Promise { } function loginResponse(jwt: string) { - const cookie = new Bun.Cookie({ ...cookieInit, value: jwt, }); + const cookie = new Bun.Cookie({ ...cookieInit, value: jwt }); return new Response('Login successful', { headers: new Headers({ 'Set-Cookie': cookie.toString(), @@ -94,6 +103,39 @@ function unAuthorizedResponse() { return new Response('Unauthorized', { status: 401, headers: logoutHeaders() }); } +const getUserSessionByTokenAndUserId = drizzleDB + .select() + .from(userSessions) + .where( + and( + eq(userSessions.token, sql.placeholder('token')), + eq(userSessions.userId, sql.placeholder('userId')) + ) + ) + .limit(1) + .prepare(); + +const deleteUserSession = drizzleDB + .delete(userSessions) + .where( + and( + eq(userSessions.token, sql.placeholder('token')), + eq(userSessions.userId, sql.placeholder('userId')) + ) + ) + .prepare(); + +const updateUserSession = drizzleDB + .update(userSessions) + .set({ maxAge: sql.placeholder('maxAge') as any }) + .where( + and( + eq(userSessions.token, sql.placeholder('token')), + eq(userSessions.userId, sql.placeholder('userId')) + ) + ) + .prepare(); + async function verify(headers: Headers) { const cookieMap = new Bun.CookieMap(headers.get('cookie') || ''); const jwtToken = cookieMap.get(jwtCookie); @@ -111,7 +153,7 @@ async function verify(headers: Headers) { maxTokenAge: `${days}d`, }); jwtPayload = jwtResult.payload as JWTPayload; - } catch (e) { + } catch (_) { // invalid token return null; } @@ -121,13 +163,10 @@ async function verify(headers: Headers) { return null; } - const result = await drizzleDB - .select() - .from(userSessions) - .where(and( - eq(userSessions.token, jwtPayload.token), - eq(userSessions.userId, jwtPayload.userId) - )); + const result = await getUserSessionByTokenAndUserId.execute({ + token: jwtPayload.token, + userId: jwtPayload.userId, + }); const session = result[0]; if (!session) { @@ -138,25 +177,21 @@ async function verify(headers: Headers) { const now = Date.now(); if (session.maxAge <= now) { // session expired - await drizzleDB - .delete(userSessions) - .where(and( - eq(userSessions.token, jwtPayload.token), - eq(userSessions.userId, jwtPayload.userId) - )); + await deleteUserSession.execute({ + token: jwtPayload.token, + userId: jwtPayload.userId, + }); return null; } if (session.maxAge <= now - renewalTime) { // renew session const newMaxAge = now + maxAge; - await drizzleDB - .update(userSessions) - .set({ maxAge: newMaxAge }) - .where(and( - eq(userSessions.token, jwtPayload.token), - eq(userSessions.userId, jwtPayload.userId) - )); + await updateUserSession.execute({ + token: jwtPayload.token, + userId: jwtPayload.userId, + maxAge: newMaxAge, + }); return new jose.SignJWT(jwtPayload) .setProtectedHeader({ alg }) @@ -193,4 +228,4 @@ export default { verify, verifyResponse, verifyFailResponse, -} +};