From 4046379185b37de3f5a40476deecb52f34289c0f Mon Sep 17 00:00:00 2001 From: themodernhakr Date: Sat, 21 Dec 2024 22:05:58 -0600 Subject: [PATCH] Initial commit --- .env.example | 1 + .gitignore | 26 +++++ .npmrc | 1 + .prettierignore | 4 + .prettierrc | 15 +++ README.md | 38 +++++++ bun.lockb | Bin 0 -> 152108 bytes drizzle.config.ts | 14 +++ eslint.config.js | 34 +++++++ package.json | 47 +++++++++ src/app.d.ts | 12 +++ src/app.html | 12 +++ src/demo.spec.ts | 7 ++ src/hooks.server.ts | 25 +++++ src/lib/index.ts | 1 + src/lib/server/auth.ts | 81 +++++++++++++++ src/lib/server/db/index.ts | 6 ++ src/lib/server/db/schema.ts | 20 ++++ src/routes/+page.svelte | 2 + src/routes/demo/+page.svelte | 1 + src/routes/demo/lucia/+page.server.ts | 22 ++++ src/routes/demo/lucia/+page.svelte | 12 +++ src/routes/demo/lucia/login/+page.server.ts | 105 ++++++++++++++++++++ src/routes/demo/lucia/login/+page.svelte | 21 ++++ static/favicon.png | Bin 0 -> 1571 bytes svelte.config.js | 18 ++++ tsconfig.json | 19 ++++ vite.config.ts | 10 ++ 28 files changed, 554 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 .npmrc create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 README.md create mode 100755 bun.lockb create mode 100644 drizzle.config.ts create mode 100644 eslint.config.js create mode 100644 package.json create mode 100644 src/app.d.ts create mode 100644 src/app.html create mode 100644 src/demo.spec.ts create mode 100644 src/hooks.server.ts create mode 100644 src/lib/index.ts create mode 100644 src/lib/server/auth.ts create mode 100644 src/lib/server/db/index.ts create mode 100644 src/lib/server/db/schema.ts create mode 100644 src/routes/+page.svelte create mode 100644 src/routes/demo/+page.svelte create mode 100644 src/routes/demo/lucia/+page.server.ts create mode 100644 src/routes/demo/lucia/+page.svelte create mode 100644 src/routes/demo/lucia/login/+page.server.ts create mode 100644 src/routes/demo/lucia/login/+page.svelte create mode 100644 static/favicon.png create mode 100644 svelte.config.js create mode 100644 tsconfig.json create mode 100644 vite.config.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..d59bf33 --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +DATABASE_URL=local.db diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..171f629 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +node_modules + +# Output +.output +.vercel +.netlify +.wrangler +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* + +# SQLite +*.db diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..ab78a95 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +# Package Managers +package-lock.json +pnpm-lock.yaml +yarn.lock diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..3f7802c --- /dev/null +++ b/.prettierrc @@ -0,0 +1,15 @@ +{ + "useTabs": true, + "singleQuote": true, + "trailingComma": "none", + "printWidth": 100, + "plugins": ["prettier-plugin-svelte"], + "overrides": [ + { + "files": "*.svelte", + "options": { + "parser": "svelte" + } + } + ] +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..b5b2950 --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +# sv + +Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli). + +## Creating a project + +If you're seeing this, you've probably already done this step. Congrats! + +```bash +# create a new project in the current directory +npx sv create + +# create a new project in my-app +npx sv create my-app +``` + +## Developing + +Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: + +```bash +npm run dev + +# or start the server and open the app in a new browser tab +npm run dev -- --open +``` + +## Building + +To create a production version of your app: + +```bash +npm run build +``` + +You can preview the production build with `npm run preview`. + +> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment. diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..33391d2d9fd76447f09525b6a913f3d88bbf9eaf GIT binary patch literal 152108 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
X~s9VN*C_Z`sf|M>9+ImR@B8q%*V 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 AhX4Qo literal 0 HcmV?d00001 diff --git a/drizzle.config.ts b/drizzle.config.ts new file mode 100644 index 0000000..e28ca0c --- /dev/null +++ b/drizzle.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'drizzle-kit'; +if (!process.env.DATABASE_URL) throw new Error('DATABASE_URL is not set'); + +export default defineConfig({ + schema: './src/lib/server/db/schema.ts', + + dbCredentials: { + url: process.env.DATABASE_URL + }, + + verbose: true, + strict: true, + dialect: 'sqlite' +}); diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..aa5987f --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,34 @@ +import prettier from 'eslint-config-prettier'; +import js from '@eslint/js'; +import { includeIgnoreFile } from '@eslint/compat'; +import svelte from 'eslint-plugin-svelte'; +import globals from 'globals'; +import { fileURLToPath } from 'node:url'; +import ts from 'typescript-eslint'; +const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url)); + +export default ts.config( + includeIgnoreFile(gitignorePath), + js.configs.recommended, + ...ts.configs.recommended, + ...svelte.configs['flat/recommended'], + prettier, + ...svelte.configs['flat/prettier'], + { + languageOptions: { + globals: { + ...globals.browser, + ...globals.node + } + } + }, + { + files: ['**/*.svelte'], + + languageOptions: { + parserOptions: { + parser: ts.parser + } + } + } +); diff --git a/package.json b/package.json new file mode 100644 index 0000000..7603766 --- /dev/null +++ b/package.json @@ -0,0 +1,47 @@ +{ + "name": "ferdinland-site-2", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "format": "prettier --write .", + "lint": "prettier --check . && eslint .", + "test:unit": "vitest", + "test": "npm run test:unit -- --run", + "db:push": "drizzle-kit push", + "db:migrate": "drizzle-kit migrate", + "db:studio": "drizzle-kit studio" + }, + "devDependencies": { + "@eslint/compat": "^1.2.3", + "@sveltejs/adapter-node": "^5.2.9", + "@sveltejs/kit": "^2.0.0", + "@sveltejs/vite-plugin-svelte": "^4.0.0", + "@types/better-sqlite3": "^7.6.11", + "drizzle-kit": "^0.22.0", + "eslint": "^9.7.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-svelte": "^2.36.0", + "globals": "^15.0.0", + "prettier": "^3.3.2", + "prettier-plugin-svelte": "^3.2.6", + "svelte": "^5.0.0", + "svelte-check": "^4.0.0", + "typescript": "^5.0.0", + "typescript-eslint": "^8.0.0", + "vite": "^5.4.11", + "vitest": "^2.0.4" + }, + "dependencies": { + "@node-rs/argon2": "^1.1.0", + "@oslojs/crypto": "^1.0.1", + "@oslojs/encoding": "^1.1.0", + "better-sqlite3": "^11.1.2", + "drizzle-orm": "^0.33.0" + } +} diff --git a/src/app.d.ts b/src/app.d.ts new file mode 100644 index 0000000..1c8a9fc --- /dev/null +++ b/src/app.d.ts @@ -0,0 +1,12 @@ +// See https://svelte.dev/docs/kit/types#app.d.ts +// for information about these interfaces +declare global { + namespace App { + interface Locals { + user: import('$lib/server/auth').SessionValidationResult['user']; + session: import('$lib/server/auth').SessionValidationResult['session']; + } + } +} + +export {}; diff --git a/src/app.html b/src/app.html new file mode 100644 index 0000000..77a5ff5 --- /dev/null +++ b/src/app.html @@ -0,0 +1,12 @@ + + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/src/demo.spec.ts b/src/demo.spec.ts new file mode 100644 index 0000000..e07cbbd --- /dev/null +++ b/src/demo.spec.ts @@ -0,0 +1,7 @@ +import { describe, it, expect } from 'vitest'; + +describe('sum test', () => { + it('adds 1 + 2 to equal 3', () => { + expect(1 + 2).toBe(3); + }); +}); diff --git a/src/hooks.server.ts b/src/hooks.server.ts new file mode 100644 index 0000000..33a84cf --- /dev/null +++ b/src/hooks.server.ts @@ -0,0 +1,25 @@ +import type { Handle } from '@sveltejs/kit'; +import * as auth from '$lib/server/auth.js'; + +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 = handleAuth; diff --git a/src/lib/index.ts b/src/lib/index.ts new file mode 100644 index 0000000..856f2b6 --- /dev/null +++ b/src/lib/index.ts @@ -0,0 +1 @@ +// place files you want to import through the `$lib` alias in this folder. diff --git a/src/lib/server/auth.ts b/src/lib/server/auth.ts new file mode 100644 index 0000000..38c9930 --- /dev/null +++ b/src/lib/server/auth.ts @@ -0,0 +1,81 @@ +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 new file mode 100644 index 0000000..205b05b --- /dev/null +++ b/src/lib/server/db/index.ts @@ -0,0 +1,6 @@ +import { drizzle } from 'drizzle-orm/better-sqlite3'; +import Database from 'better-sqlite3'; +import { env } from '$env/dynamic/private'; +if (!env.DATABASE_URL) throw new Error('DATABASE_URL is not set'); +const client = new Database(env.DATABASE_URL); +export const db = drizzle(client); diff --git a/src/lib/server/db/schema.ts b/src/lib/server/db/schema.ts new file mode 100644 index 0000000..166d477 --- /dev/null +++ b/src/lib/server/db/schema.ts @@ -0,0 +1,20 @@ +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/+page.svelte b/src/routes/+page.svelte new file mode 100644 index 0000000..cc88df0 --- /dev/null +++ b/src/routes/+page.svelte @@ -0,0 +1,2 @@ +

Welcome to SvelteKit

+

Visit svelte.dev/docs/kit to read the documentation

diff --git a/src/routes/demo/+page.svelte b/src/routes/demo/+page.svelte new file mode 100644 index 0000000..e291ab8 --- /dev/null +++ b/src/routes/demo/+page.svelte @@ -0,0 +1 @@ +lucia diff --git a/src/routes/demo/lucia/+page.server.ts b/src/routes/demo/lucia/+page.server.ts new file mode 100644 index 0000000..c6b3d98 --- /dev/null +++ b/src/routes/demo/lucia/+page.server.ts @@ -0,0 +1,22 @@ +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 new file mode 100644 index 0000000..cefb2d1 --- /dev/null +++ b/src/routes/demo/lucia/+page.svelte @@ -0,0 +1,12 @@ + + +

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 new file mode 100644 index 0000000..28adcb7 --- /dev/null +++ b/src/routes/demo/lucia/login/+page.server.ts @@ -0,0 +1,105 @@ +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' }); + } + if (!validatePassword(password)) { + return fail(400, { message: 'Invalid password' }); + } + + 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 new file mode 100644 index 0000000..a3138d7 --- /dev/null +++ b/src/routes/demo/lucia/login/+page.svelte @@ -0,0 +1,21 @@ + + +

Login/Register

+
+ + + + +
+

{form?.message ?? ''}

diff --git a/static/favicon.png b/static/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..825b9e65af7c104cfb07089bb28659393b4f2097 GIT binary patch literal 1571 zcmV+;2Hg3HP)Px)-AP12RCwC$UE6KzI1p6{F2N z1VK2vi|pOpn{~#djwYcWXTI_im_u^TJgMZ4JMOsSj!0ma>B?-(Hr@X&W@|R-$}W@Z zgj#$x=!~7LGqHW?IO8+*oE1MyDp!G=L0#^lUx?;!fXv@l^6SvTnf^ac{5OurzC#ZMYc20lI%HhX816AYVs1T3heS1*WaWH z%;x>)-J}YB5#CLzU@GBR6sXYrD>Vw(Fmt#|JP;+}<#6b63Ike{Fuo!?M{yEffez;| zp!PfsuaC)>h>-AdbnwN13g*1LowNjT5?+lFVd#9$!8Z9HA|$*6dQ8EHLu}U|obW6f z2%uGv?vr=KNq7YYa2Roj;|zooo<)lf=&2yxM@e`kM$CmCR#x>gI>I|*Ubr({5Y^rb zghxQU22N}F51}^yfDSt786oMTc!W&V;d?76)9KXX1 z+6Okem(d}YXmmOiZq$!IPk5t8nnS{%?+vDFz3BevmFNgpIod~R{>@#@5x9zJKEHLHv!gHeK~n)Ld!M8DB|Kfe%~123&Hz1Z(86nU7*G5chmyDe ziV7$pB7pJ=96hpxHv9rCR29%bLOXlKU<_13_M8x)6;P8E1Kz6G<&P?$P^%c!M5`2` zfY2zg;VK5~^>TJGQzc+33-n~gKt{{of8GzUkWmU110IgI0DLxRIM>0US|TsM=L|@F z0Bun8U!cRB7-2apz=y-7*UxOxz@Z0)@QM)9wSGki1AZ38ceG7Q72z5`i;i=J`ILzL z@iUO?SBBG-0cQuo+an4TsLy-g-x;8P4UVwk|D8{W@U1Zi z!M)+jqy@nQ$p?5tsHp-6J304Q={v-B>66$P0IDx&YT(`IcZ~bZfmn11#rXd7<5s}y zBi9eim&zQc0Dk|2>$bs0PnLmDfMP5lcXRY&cvJ=zKxI^f0%-d$tD!`LBf9^jMSYUA zI8U?CWdY@}cRq6{5~y+)#h1!*-HcGW@+gZ4B};0OnC~`xQOyH19z*TA!!BJ%9s0V3F?CAJ{hTd#*tf+ur-W9MOURF-@B77_-OshsY}6 zOXRY=5%C^*26z?l)1=$bz30!so5tfABdSYzO+H=CpV~aaUefmjvfZ3Ttu9W&W3Iu6 zROlh0MFA5h;my}8lB0tAV-Rvc2Zs_CCSJnx@d`**$idgy-iMob4dJWWw|21b4NB=LfsYp0Aeh{Ov)yztQi;eL4y5 zMi>8^SzKqk8~k?UiQK^^-5d8c%bV?$F8%X~czyiaKCI2=UH