Auth is actually working
This commit is contained in:
parent
32281aa1ff
commit
15ff9609c4
@ -11,7 +11,7 @@ localhost {
|
|||||||
|
|
||||||
handle {
|
handle {
|
||||||
reverse_proxy dev:5173 {
|
reverse_proxy dev:5173 {
|
||||||
header_up Host {host}
|
header_up Origin "^https://localhost$" "http://localhost"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
CREATE TABLE "sessions" (
|
CREATE TABLE "sessions" (
|
||||||
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "sessions_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
"user_id" integer,
|
"user_id" integer NOT NULL,
|
||||||
"expires_at" timestamp with time zone NOT NULL
|
"expires_at" timestamp with time zone NOT NULL
|
||||||
);
|
);
|
||||||
--> statement-breakpoint
|
--> statement-breakpoint
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"id": "f22311a8-ce92-4bd5-9a7c-5e8085af13a9",
|
"id": "9f31c19a-9e3c-41db-a08b-27d9916265cb",
|
||||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"dialect": "postgresql",
|
"dialect": "postgresql",
|
||||||
@ -10,26 +10,15 @@
|
|||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"type": "integer",
|
"type": "text",
|
||||||
"primaryKey": true,
|
"primaryKey": true,
|
||||||
"notNull": true,
|
"notNull": true
|
||||||
"identity": {
|
|
||||||
"type": "always",
|
|
||||||
"name": "sessions_id_seq",
|
|
||||||
"schema": "public",
|
|
||||||
"increment": "1",
|
|
||||||
"startWith": "1",
|
|
||||||
"minValue": "1",
|
|
||||||
"maxValue": "2147483647",
|
|
||||||
"cache": "1",
|
|
||||||
"cycle": false
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"user_id": {
|
"user_id": {
|
||||||
"name": "user_id",
|
"name": "user_id",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": true
|
||||||
},
|
},
|
||||||
"expires_at": {
|
"expires_at": {
|
||||||
"name": "expires_at",
|
"name": "expires_at",
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
{
|
{
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"when": 1738177092891,
|
"when": 1738197417069,
|
||||||
"tag": "0000_users-and-sessions",
|
"tag": "0000_users-and-sessions",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
|
8
index.js
8
index.js
@ -1,8 +0,0 @@
|
|||||||
import { loadEnv } from 'vite';
|
|
||||||
|
|
||||||
console.log(process.env);
|
|
||||||
|
|
||||||
const env = loadEnv("development", ".", "");
|
|
||||||
|
|
||||||
// console.log(env);
|
|
||||||
// console.log(process.env)
|
|
10
package.json
10
package.json
@ -16,11 +16,14 @@
|
|||||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||||
"test:unit": "vitest",
|
"test:unit": "vitest",
|
||||||
"test": "npm run test:unit -- --run"
|
"test": "npm run test:unit -- --run",
|
||||||
|
"db:push": "drizzle-kit push",
|
||||||
|
"db:migrate": "drizzle-kit migrate",
|
||||||
|
"db:studio": "drizzle-kit studio"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "1.9.4",
|
"@biomejs/biome": "1.9.4",
|
||||||
"@sveltejs/adapter-auto": "^4.0.0",
|
"@sveltejs/adapter-node": "^5.2.12",
|
||||||
"@sveltejs/kit": "^2.16.1",
|
"@sveltejs/kit": "^2.16.1",
|
||||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||||
"@types/pg": "^8.11.11",
|
"@types/pg": "^8.11.11",
|
||||||
@ -36,7 +39,10 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@devcontainers/cli": "^0.73.0",
|
"@devcontainers/cli": "^0.73.0",
|
||||||
|
"@js-temporal/polyfill": "^0.4.4",
|
||||||
"@node-rs/argon2": "^2.0.2",
|
"@node-rs/argon2": "^2.0.2",
|
||||||
|
"@oslojs/crypto": "^1.0.1",
|
||||||
|
"@oslojs/encoding": "^1.1.0",
|
||||||
"drizzle-orm": "^0.39.0",
|
"drizzle-orm": "^0.39.0",
|
||||||
"pg": "^8.13.1"
|
"pg": "^8.13.1"
|
||||||
}
|
}
|
||||||
|
215
pnpm-lock.yaml
generated
215
pnpm-lock.yaml
generated
@ -11,9 +11,18 @@ importers:
|
|||||||
'@devcontainers/cli':
|
'@devcontainers/cli':
|
||||||
specifier: ^0.73.0
|
specifier: ^0.73.0
|
||||||
version: 0.73.0
|
version: 0.73.0
|
||||||
|
'@js-temporal/polyfill':
|
||||||
|
specifier: ^0.4.4
|
||||||
|
version: 0.4.4
|
||||||
'@node-rs/argon2':
|
'@node-rs/argon2':
|
||||||
specifier: ^2.0.2
|
specifier: ^2.0.2
|
||||||
version: 2.0.2
|
version: 2.0.2
|
||||||
|
'@oslojs/crypto':
|
||||||
|
specifier: ^1.0.1
|
||||||
|
version: 1.0.1
|
||||||
|
'@oslojs/encoding':
|
||||||
|
specifier: ^1.1.0
|
||||||
|
version: 1.1.0
|
||||||
drizzle-orm:
|
drizzle-orm:
|
||||||
specifier: ^0.39.0
|
specifier: ^0.39.0
|
||||||
version: 0.39.0(@types/pg@8.11.11)(pg@8.13.1)
|
version: 0.39.0(@types/pg@8.11.11)(pg@8.13.1)
|
||||||
@ -24,9 +33,9 @@ importers:
|
|||||||
'@biomejs/biome':
|
'@biomejs/biome':
|
||||||
specifier: 1.9.4
|
specifier: 1.9.4
|
||||||
version: 1.9.4
|
version: 1.9.4
|
||||||
'@sveltejs/adapter-auto':
|
'@sveltejs/adapter-node':
|
||||||
specifier: ^4.0.0
|
specifier: ^5.2.12
|
||||||
version: 4.0.0(@sveltejs/kit@2.16.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.19.5)(vite@6.0.11(@types/node@22.12.0)(tsx@4.19.2)))(svelte@5.19.5)(vite@6.0.11(@types/node@22.12.0)(tsx@4.19.2)))
|
version: 5.2.12(@sveltejs/kit@2.16.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.19.5)(vite@6.0.11(@types/node@22.12.0)(tsx@4.19.2)))(svelte@5.19.5)(vite@6.0.11(@types/node@22.12.0)(tsx@4.19.2)))
|
||||||
'@sveltejs/kit':
|
'@sveltejs/kit':
|
||||||
specifier: ^2.16.1
|
specifier: ^2.16.1
|
||||||
version: 2.16.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.19.5)(vite@6.0.11(@types/node@22.12.0)(tsx@4.19.2)))(svelte@5.19.5)(vite@6.0.11(@types/node@22.12.0)(tsx@4.19.2))
|
version: 2.16.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.19.5)(vite@6.0.11(@types/node@22.12.0)(tsx@4.19.2)))(svelte@5.19.5)(vite@6.0.11(@types/node@22.12.0)(tsx@4.19.2))
|
||||||
@ -730,6 +739,10 @@ packages:
|
|||||||
'@jridgewell/trace-mapping@0.3.25':
|
'@jridgewell/trace-mapping@0.3.25':
|
||||||
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
|
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
|
||||||
|
|
||||||
|
'@js-temporal/polyfill@0.4.4':
|
||||||
|
resolution: {integrity: sha512-2X6bvghJ/JAoZO52lbgyAPFj8uCflhTo2g7nkFzEQdXd/D8rEeD4HtmTEpmtGCva260fcd66YNXBOYdnmHqSOg==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
'@napi-rs/wasm-runtime@0.2.6':
|
'@napi-rs/wasm-runtime@0.2.6':
|
||||||
resolution: {integrity: sha512-z8YVS3XszxFTO73iwvFDNpQIzdMmSDTP/mB3E/ucR37V3Sx57hSExcXyMoNwaucWxnsWf4xfbZv0iZ30jr0M4Q==}
|
resolution: {integrity: sha512-z8YVS3XszxFTO73iwvFDNpQIzdMmSDTP/mB3E/ucR37V3Sx57hSExcXyMoNwaucWxnsWf4xfbZv0iZ30jr0M4Q==}
|
||||||
|
|
||||||
@ -820,9 +833,57 @@ packages:
|
|||||||
resolution: {integrity: sha512-t64wIsPEtNd4aUPuTAyeL2ubxATCBGmeluaKXEMAFk/8w6AJIVVkeLKMBpgLW6LU2t5cQxT+env/c6jxbtTQBg==}
|
resolution: {integrity: sha512-t64wIsPEtNd4aUPuTAyeL2ubxATCBGmeluaKXEMAFk/8w6AJIVVkeLKMBpgLW6LU2t5cQxT+env/c6jxbtTQBg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
|
|
||||||
|
'@oslojs/asn1@1.0.0':
|
||||||
|
resolution: {integrity: sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA==}
|
||||||
|
|
||||||
|
'@oslojs/binary@1.0.0':
|
||||||
|
resolution: {integrity: sha512-9RCU6OwXU6p67H4NODbuxv2S3eenuQ4/WFLrsq+K/k682xrznH5EVWA7N4VFk9VYVcbFtKqur5YQQZc0ySGhsQ==}
|
||||||
|
|
||||||
|
'@oslojs/crypto@1.0.1':
|
||||||
|
resolution: {integrity: sha512-7n08G8nWjAr/Yu3vu9zzrd0L9XnrJfpMioQcvCMxBIiF5orECHe5/3J0jmXRVvgfqMm/+4oxlQ+Sq39COYLcNQ==}
|
||||||
|
|
||||||
|
'@oslojs/encoding@1.1.0':
|
||||||
|
resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==}
|
||||||
|
|
||||||
'@polka/url@1.0.0-next.28':
|
'@polka/url@1.0.0-next.28':
|
||||||
resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==}
|
resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==}
|
||||||
|
|
||||||
|
'@rollup/plugin-commonjs@28.0.2':
|
||||||
|
resolution: {integrity: sha512-BEFI2EDqzl+vA1rl97IDRZ61AIwGH093d9nz8+dThxJNH8oSoB7MjWvPCX3dkaK1/RCJ/1v/R1XB15FuSs0fQw==}
|
||||||
|
engines: {node: '>=16.0.0 || 14 >= 14.17'}
|
||||||
|
peerDependencies:
|
||||||
|
rollup: ^2.68.0||^3.0.0||^4.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
rollup:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/plugin-json@6.1.0':
|
||||||
|
resolution: {integrity: sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
rollup:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/plugin-node-resolve@16.0.0':
|
||||||
|
resolution: {integrity: sha512-0FPvAeVUT/zdWoO0jnb/V5BlBsUSNfkIOtFHzMO4H9MOklrmQFY6FduVHKucNb/aTFxvnGhj4MNj/T1oNdDfNg==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
rollup: ^2.78.0||^3.0.0||^4.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
rollup:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/pluginutils@5.1.4':
|
||||||
|
resolution: {integrity: sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
rollup:
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@rollup/rollup-android-arm-eabi@4.32.1':
|
'@rollup/rollup-android-arm-eabi@4.32.1':
|
||||||
resolution: {integrity: sha512-/pqA4DmqyCm8u5YIDzIdlLcEmuvxb0v8fZdFhVMszSpDTgbQKdw3/mB3eMUHIbubtJ6F9j+LtmyCnHTEqIHyzA==}
|
resolution: {integrity: sha512-/pqA4DmqyCm8u5YIDzIdlLcEmuvxb0v8fZdFhVMszSpDTgbQKdw3/mB3eMUHIbubtJ6F9j+LtmyCnHTEqIHyzA==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
@ -918,10 +979,10 @@ packages:
|
|||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@sveltejs/adapter-auto@4.0.0':
|
'@sveltejs/adapter-node@5.2.12':
|
||||||
resolution: {integrity: sha512-kmuYSQdD2AwThymQF0haQhM8rE5rhutQXG4LNbnbShwhMO4qQGnKaaTy+88DuNSuoQDi58+thpq8XpHc1+oEKQ==}
|
resolution: {integrity: sha512-0bp4Yb3jKIEcZWVcJC/L1xXp9zzJS4hDwfb4VITAkfT4OVdkspSHsx7YhqJDbb2hgLl6R9Vs7VQR+fqIVOxPUQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@sveltejs/kit': ^2.0.0
|
'@sveltejs/kit': ^2.4.0
|
||||||
|
|
||||||
'@sveltejs/kit@2.16.1':
|
'@sveltejs/kit@2.16.1':
|
||||||
resolution: {integrity: sha512-2pF5sgGJx9brYZ/9nNDYnh5KX0JguPF14dnvvtf/MqrvlWrDj/e7Rk3LBJPecFLLK1GRs6ZniD24gFPqZm/NFw==}
|
resolution: {integrity: sha512-2pF5sgGJx9brYZ/9nNDYnh5KX0JguPF14dnvvtf/MqrvlWrDj/e7Rk3LBJPecFLLK1GRs6ZniD24gFPqZm/NFw==}
|
||||||
@ -962,6 +1023,9 @@ packages:
|
|||||||
'@types/pg@8.11.11':
|
'@types/pg@8.11.11':
|
||||||
resolution: {integrity: sha512-kGT1qKM8wJQ5qlawUrEkXgvMSXoV213KfMGXcwfDwUIfUHXqXYXOfS1nE1LINRJVVVx5wCm70XnFlMHaIcQAfw==}
|
resolution: {integrity: sha512-kGT1qKM8wJQ5qlawUrEkXgvMSXoV213KfMGXcwfDwUIfUHXqXYXOfS1nE1LINRJVVVx5wCm70XnFlMHaIcQAfw==}
|
||||||
|
|
||||||
|
'@types/resolve@1.20.2':
|
||||||
|
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
|
||||||
|
|
||||||
'@vitest/expect@3.0.4':
|
'@vitest/expect@3.0.4':
|
||||||
resolution: {integrity: sha512-Nm5kJmYw6P2BxhJPkO3eKKhGYKRsnqJqf+r0yOGRKpEP+bSCBDsjXgiu1/5QFrnPMEgzfC38ZEjvCFgaNBC0Eg==}
|
resolution: {integrity: sha512-Nm5kJmYw6P2BxhJPkO3eKKhGYKRsnqJqf+r0yOGRKpEP+bSCBDsjXgiu1/5QFrnPMEgzfC38ZEjvCFgaNBC0Eg==}
|
||||||
|
|
||||||
@ -1036,6 +1100,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
commondir@1.0.1:
|
||||||
|
resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==}
|
||||||
|
|
||||||
cookie@0.6.0:
|
cookie@0.6.0:
|
||||||
resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==}
|
resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
@ -1198,6 +1265,9 @@ packages:
|
|||||||
esrap@1.4.3:
|
esrap@1.4.3:
|
||||||
resolution: {integrity: sha512-Xddc1RsoFJ4z9nR7W7BFaEPIp4UXoeQ0+077UdWLxbafMQFyU79sQJMk7kxNgRwQ9/aVgaKacCHC2pUACGwmYw==}
|
resolution: {integrity: sha512-Xddc1RsoFJ4z9nR7W7BFaEPIp4UXoeQ0+077UdWLxbafMQFyU79sQJMk7kxNgRwQ9/aVgaKacCHC2pUACGwmYw==}
|
||||||
|
|
||||||
|
estree-walker@2.0.2:
|
||||||
|
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
|
||||||
|
|
||||||
estree-walker@3.0.3:
|
estree-walker@3.0.3:
|
||||||
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
|
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
|
||||||
|
|
||||||
@ -1218,15 +1288,35 @@ packages:
|
|||||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
|
function-bind@1.1.2:
|
||||||
|
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||||
|
|
||||||
get-tsconfig@4.10.0:
|
get-tsconfig@4.10.0:
|
||||||
resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==}
|
resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==}
|
||||||
|
|
||||||
|
hasown@2.0.2:
|
||||||
|
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
import-meta-resolve@4.1.0:
|
import-meta-resolve@4.1.0:
|
||||||
resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==}
|
resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==}
|
||||||
|
|
||||||
|
is-core-module@2.16.1:
|
||||||
|
resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
is-module@1.0.0:
|
||||||
|
resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==}
|
||||||
|
|
||||||
|
is-reference@1.2.1:
|
||||||
|
resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==}
|
||||||
|
|
||||||
is-reference@3.0.3:
|
is-reference@3.0.3:
|
||||||
resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==}
|
resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==}
|
||||||
|
|
||||||
|
jsbi@4.3.0:
|
||||||
|
resolution: {integrity: sha512-SnZNcinB4RIcnEyZqFPdGPVgrg2AcnykiBy0sHVJQKHYeaLUvi3Exj+iaPpLnFVkDPZIV4U0yvgC9/R4uEAZ9g==}
|
||||||
|
|
||||||
kleur@4.1.5:
|
kleur@4.1.5:
|
||||||
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
|
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@ -1259,6 +1349,9 @@ packages:
|
|||||||
obuf@1.1.2:
|
obuf@1.1.2:
|
||||||
resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==}
|
resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==}
|
||||||
|
|
||||||
|
path-parse@1.0.7:
|
||||||
|
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
||||||
|
|
||||||
pathe@2.0.2:
|
pathe@2.0.2:
|
||||||
resolution: {integrity: sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==}
|
resolution: {integrity: sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==}
|
||||||
|
|
||||||
@ -1361,6 +1454,11 @@ packages:
|
|||||||
resolve-pkg-maps@1.0.0:
|
resolve-pkg-maps@1.0.0:
|
||||||
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
|
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
|
||||||
|
|
||||||
|
resolve@1.22.10:
|
||||||
|
resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
rollup@4.32.1:
|
rollup@4.32.1:
|
||||||
resolution: {integrity: sha512-z+aeEsOeEa3mEbS1Tjl6sAZ8NE3+AalQz1RJGj81M+fizusbdDMoEJwdJNHfaB40Scr4qNu+welOfes7maKonA==}
|
resolution: {integrity: sha512-z+aeEsOeEa3mEbS1Tjl6sAZ8NE3+AalQz1RJGj81M+fizusbdDMoEJwdJNHfaB40Scr4qNu+welOfes7maKonA==}
|
||||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||||
@ -1401,6 +1499,10 @@ packages:
|
|||||||
std-env@3.8.0:
|
std-env@3.8.0:
|
||||||
resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==}
|
resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==}
|
||||||
|
|
||||||
|
supports-preserve-symlinks-flag@1.0.0:
|
||||||
|
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
svelte-check@4.1.4:
|
svelte-check@4.1.4:
|
||||||
resolution: {integrity: sha512-v0j7yLbT29MezzaQJPEDwksybTE2Ups9rUxEXy92T06TiA0cbqcO8wAOwNUVkFW6B0hsYHA+oAX3BS8b/2oHtw==}
|
resolution: {integrity: sha512-v0j7yLbT29MezzaQJPEDwksybTE2Ups9rUxEXy92T06TiA0cbqcO8wAOwNUVkFW6B0hsYHA+oAX3BS8b/2oHtw==}
|
||||||
engines: {node: '>= 18.0.0'}
|
engines: {node: '>= 18.0.0'}
|
||||||
@ -1915,6 +2017,11 @@ snapshots:
|
|||||||
'@jridgewell/resolve-uri': 3.1.2
|
'@jridgewell/resolve-uri': 3.1.2
|
||||||
'@jridgewell/sourcemap-codec': 1.5.0
|
'@jridgewell/sourcemap-codec': 1.5.0
|
||||||
|
|
||||||
|
'@js-temporal/polyfill@0.4.4':
|
||||||
|
dependencies:
|
||||||
|
jsbi: 4.3.0
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@napi-rs/wasm-runtime@0.2.6':
|
'@napi-rs/wasm-runtime@0.2.6':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@emnapi/core': 1.3.1
|
'@emnapi/core': 1.3.1
|
||||||
@ -1983,8 +2090,57 @@ snapshots:
|
|||||||
'@node-rs/argon2-win32-ia32-msvc': 2.0.2
|
'@node-rs/argon2-win32-ia32-msvc': 2.0.2
|
||||||
'@node-rs/argon2-win32-x64-msvc': 2.0.2
|
'@node-rs/argon2-win32-x64-msvc': 2.0.2
|
||||||
|
|
||||||
|
'@oslojs/asn1@1.0.0':
|
||||||
|
dependencies:
|
||||||
|
'@oslojs/binary': 1.0.0
|
||||||
|
|
||||||
|
'@oslojs/binary@1.0.0': {}
|
||||||
|
|
||||||
|
'@oslojs/crypto@1.0.1':
|
||||||
|
dependencies:
|
||||||
|
'@oslojs/asn1': 1.0.0
|
||||||
|
'@oslojs/binary': 1.0.0
|
||||||
|
|
||||||
|
'@oslojs/encoding@1.1.0': {}
|
||||||
|
|
||||||
'@polka/url@1.0.0-next.28': {}
|
'@polka/url@1.0.0-next.28': {}
|
||||||
|
|
||||||
|
'@rollup/plugin-commonjs@28.0.2(rollup@4.32.1)':
|
||||||
|
dependencies:
|
||||||
|
'@rollup/pluginutils': 5.1.4(rollup@4.32.1)
|
||||||
|
commondir: 1.0.1
|
||||||
|
estree-walker: 2.0.2
|
||||||
|
fdir: 6.4.3(picomatch@4.0.2)
|
||||||
|
is-reference: 1.2.1
|
||||||
|
magic-string: 0.30.17
|
||||||
|
picomatch: 4.0.2
|
||||||
|
optionalDependencies:
|
||||||
|
rollup: 4.32.1
|
||||||
|
|
||||||
|
'@rollup/plugin-json@6.1.0(rollup@4.32.1)':
|
||||||
|
dependencies:
|
||||||
|
'@rollup/pluginutils': 5.1.4(rollup@4.32.1)
|
||||||
|
optionalDependencies:
|
||||||
|
rollup: 4.32.1
|
||||||
|
|
||||||
|
'@rollup/plugin-node-resolve@16.0.0(rollup@4.32.1)':
|
||||||
|
dependencies:
|
||||||
|
'@rollup/pluginutils': 5.1.4(rollup@4.32.1)
|
||||||
|
'@types/resolve': 1.20.2
|
||||||
|
deepmerge: 4.3.1
|
||||||
|
is-module: 1.0.0
|
||||||
|
resolve: 1.22.10
|
||||||
|
optionalDependencies:
|
||||||
|
rollup: 4.32.1
|
||||||
|
|
||||||
|
'@rollup/pluginutils@5.1.4(rollup@4.32.1)':
|
||||||
|
dependencies:
|
||||||
|
'@types/estree': 1.0.6
|
||||||
|
estree-walker: 2.0.2
|
||||||
|
picomatch: 4.0.2
|
||||||
|
optionalDependencies:
|
||||||
|
rollup: 4.32.1
|
||||||
|
|
||||||
'@rollup/rollup-android-arm-eabi@4.32.1':
|
'@rollup/rollup-android-arm-eabi@4.32.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@ -2042,10 +2198,13 @@ snapshots:
|
|||||||
'@rollup/rollup-win32-x64-msvc@4.32.1':
|
'@rollup/rollup-win32-x64-msvc@4.32.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@sveltejs/adapter-auto@4.0.0(@sveltejs/kit@2.16.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.19.5)(vite@6.0.11(@types/node@22.12.0)(tsx@4.19.2)))(svelte@5.19.5)(vite@6.0.11(@types/node@22.12.0)(tsx@4.19.2)))':
|
'@sveltejs/adapter-node@5.2.12(@sveltejs/kit@2.16.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.19.5)(vite@6.0.11(@types/node@22.12.0)(tsx@4.19.2)))(svelte@5.19.5)(vite@6.0.11(@types/node@22.12.0)(tsx@4.19.2)))':
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@rollup/plugin-commonjs': 28.0.2(rollup@4.32.1)
|
||||||
|
'@rollup/plugin-json': 6.1.0(rollup@4.32.1)
|
||||||
|
'@rollup/plugin-node-resolve': 16.0.0(rollup@4.32.1)
|
||||||
'@sveltejs/kit': 2.16.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.19.5)(vite@6.0.11(@types/node@22.12.0)(tsx@4.19.2)))(svelte@5.19.5)(vite@6.0.11(@types/node@22.12.0)(tsx@4.19.2))
|
'@sveltejs/kit': 2.16.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.19.5)(vite@6.0.11(@types/node@22.12.0)(tsx@4.19.2)))(svelte@5.19.5)(vite@6.0.11(@types/node@22.12.0)(tsx@4.19.2))
|
||||||
import-meta-resolve: 4.1.0
|
rollup: 4.32.1
|
||||||
|
|
||||||
'@sveltejs/kit@2.16.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.19.5)(vite@6.0.11(@types/node@22.12.0)(tsx@4.19.2)))(svelte@5.19.5)(vite@6.0.11(@types/node@22.12.0)(tsx@4.19.2))':
|
'@sveltejs/kit@2.16.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.19.5)(vite@6.0.11(@types/node@22.12.0)(tsx@4.19.2)))(svelte@5.19.5)(vite@6.0.11(@types/node@22.12.0)(tsx@4.19.2))':
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -2105,6 +2264,8 @@ snapshots:
|
|||||||
pg-protocol: 1.7.0
|
pg-protocol: 1.7.0
|
||||||
pg-types: 4.0.2
|
pg-types: 4.0.2
|
||||||
|
|
||||||
|
'@types/resolve@1.20.2': {}
|
||||||
|
|
||||||
'@vitest/expect@3.0.4':
|
'@vitest/expect@3.0.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/spy': 3.0.4
|
'@vitest/spy': 3.0.4
|
||||||
@ -2177,6 +2338,8 @@ snapshots:
|
|||||||
|
|
||||||
clsx@2.1.1: {}
|
clsx@2.1.1: {}
|
||||||
|
|
||||||
|
commondir@1.0.1: {}
|
||||||
|
|
||||||
cookie@0.6.0: {}
|
cookie@0.6.0: {}
|
||||||
|
|
||||||
debug@4.4.0:
|
debug@4.4.0:
|
||||||
@ -2330,6 +2493,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/sourcemap-codec': 1.5.0
|
'@jridgewell/sourcemap-codec': 1.5.0
|
||||||
|
|
||||||
|
estree-walker@2.0.2: {}
|
||||||
|
|
||||||
estree-walker@3.0.3:
|
estree-walker@3.0.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/estree': 1.0.6
|
'@types/estree': 1.0.6
|
||||||
@ -2343,16 +2508,34 @@ snapshots:
|
|||||||
fsevents@2.3.3:
|
fsevents@2.3.3:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
function-bind@1.1.2: {}
|
||||||
|
|
||||||
get-tsconfig@4.10.0:
|
get-tsconfig@4.10.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
resolve-pkg-maps: 1.0.0
|
resolve-pkg-maps: 1.0.0
|
||||||
|
|
||||||
|
hasown@2.0.2:
|
||||||
|
dependencies:
|
||||||
|
function-bind: 1.1.2
|
||||||
|
|
||||||
import-meta-resolve@4.1.0: {}
|
import-meta-resolve@4.1.0: {}
|
||||||
|
|
||||||
|
is-core-module@2.16.1:
|
||||||
|
dependencies:
|
||||||
|
hasown: 2.0.2
|
||||||
|
|
||||||
|
is-module@1.0.0: {}
|
||||||
|
|
||||||
|
is-reference@1.2.1:
|
||||||
|
dependencies:
|
||||||
|
'@types/estree': 1.0.6
|
||||||
|
|
||||||
is-reference@3.0.3:
|
is-reference@3.0.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/estree': 1.0.6
|
'@types/estree': 1.0.6
|
||||||
|
|
||||||
|
jsbi@4.3.0: {}
|
||||||
|
|
||||||
kleur@4.1.5: {}
|
kleur@4.1.5: {}
|
||||||
|
|
||||||
locate-character@3.0.0: {}
|
locate-character@3.0.0: {}
|
||||||
@ -2373,6 +2556,8 @@ snapshots:
|
|||||||
|
|
||||||
obuf@1.1.2: {}
|
obuf@1.1.2: {}
|
||||||
|
|
||||||
|
path-parse@1.0.7: {}
|
||||||
|
|
||||||
pathe@2.0.2: {}
|
pathe@2.0.2: {}
|
||||||
|
|
||||||
pathval@2.0.0: {}
|
pathval@2.0.0: {}
|
||||||
@ -2426,8 +2611,7 @@ snapshots:
|
|||||||
|
|
||||||
picocolors@1.1.1: {}
|
picocolors@1.1.1: {}
|
||||||
|
|
||||||
picomatch@4.0.2:
|
picomatch@4.0.2: {}
|
||||||
optional: true
|
|
||||||
|
|
||||||
postcss@8.5.1:
|
postcss@8.5.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -2461,6 +2645,12 @@ snapshots:
|
|||||||
|
|
||||||
resolve-pkg-maps@1.0.0: {}
|
resolve-pkg-maps@1.0.0: {}
|
||||||
|
|
||||||
|
resolve@1.22.10:
|
||||||
|
dependencies:
|
||||||
|
is-core-module: 2.16.1
|
||||||
|
path-parse: 1.0.7
|
||||||
|
supports-preserve-symlinks-flag: 1.0.0
|
||||||
|
|
||||||
rollup@4.32.1:
|
rollup@4.32.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/estree': 1.0.6
|
'@types/estree': 1.0.6
|
||||||
@ -2515,6 +2705,8 @@ snapshots:
|
|||||||
|
|
||||||
std-env@3.8.0: {}
|
std-env@3.8.0: {}
|
||||||
|
|
||||||
|
supports-preserve-symlinks-flag@1.0.0: {}
|
||||||
|
|
||||||
svelte-check@4.1.4(picomatch@4.0.2)(svelte@5.19.5)(typescript@5.7.3):
|
svelte-check@4.1.4(picomatch@4.0.2)(svelte@5.19.5)(typescript@5.7.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/trace-mapping': 0.3.25
|
'@jridgewell/trace-mapping': 0.3.25
|
||||||
@ -2556,8 +2748,7 @@ snapshots:
|
|||||||
|
|
||||||
totalist@3.0.1: {}
|
totalist@3.0.1: {}
|
||||||
|
|
||||||
tslib@2.8.1:
|
tslib@2.8.1: {}
|
||||||
optional: true
|
|
||||||
|
|
||||||
tsx@4.19.2:
|
tsx@4.19.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
70
src/hooks.server.ts
Normal file
70
src/hooks.server.ts
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import { RefillingTokenBucket } from "$lib/server/rate-limit";
|
||||||
|
import {
|
||||||
|
validateSessionToken,
|
||||||
|
setSessionTokenCookie,
|
||||||
|
deleteSessionTokenCookie,
|
||||||
|
} from "$lib/server/session";
|
||||||
|
import { sequence } from "@sveltejs/kit/hooks";
|
||||||
|
|
||||||
|
import { redirect, type Handle } from "@sveltejs/kit";
|
||||||
|
|
||||||
|
const bucket = new RefillingTokenBucket<string>(100, 1);
|
||||||
|
|
||||||
|
const rateLimitHandle: Handle = async ({ event, resolve }) => {
|
||||||
|
// Note: Assumes X-Forwarded-For will always be defined.
|
||||||
|
const clientIP = event.request.headers.get("X-Forwarded-For");
|
||||||
|
if (clientIP === null) {
|
||||||
|
return resolve(event);
|
||||||
|
}
|
||||||
|
let cost: number;
|
||||||
|
if (event.request.method === "GET" || event.request.method === "OPTIONS") {
|
||||||
|
cost = 1;
|
||||||
|
} else {
|
||||||
|
cost = 3;
|
||||||
|
}
|
||||||
|
if (!bucket.consume(clientIP, cost)) {
|
||||||
|
return new Response("Too many requests", {
|
||||||
|
status: 429,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return resolve(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
const authHandle: Handle = async ({ event, resolve }) => {
|
||||||
|
const token = event.cookies.get("session") ?? null;
|
||||||
|
if (token === null) {
|
||||||
|
event.locals.user = null;
|
||||||
|
event.locals.session = null;
|
||||||
|
return resolve(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { session, user } = await validateSessionToken(token);
|
||||||
|
if (session !== null) {
|
||||||
|
setSessionTokenCookie(event, token, session.expiresAt);
|
||||||
|
} else {
|
||||||
|
deleteSessionTokenCookie(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
event.locals.session = session;
|
||||||
|
event.locals.user = user;
|
||||||
|
return resolve(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
const enforceAuthenticated: Handle = async ({ event, resolve }) => {
|
||||||
|
const routeExceptions = ["/login"];
|
||||||
|
if (event.route.id !== null && routeExceptions.includes(event.route.id)) {
|
||||||
|
return resolve(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(event.locals.session && event.locals.user)) {
|
||||||
|
redirect(303, "/login");
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolve(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const handle = sequence(
|
||||||
|
rateLimitHandle,
|
||||||
|
authHandle,
|
||||||
|
enforceAuthenticated,
|
||||||
|
);
|
@ -1,10 +1,10 @@
|
|||||||
import { DATABASE_PASSWORD } from "$env/static/private";
|
import { DATABASE_URL } from "$env/static/private";
|
||||||
import { drizzle } from "drizzle-orm/node-postgres";
|
import { drizzle } from "drizzle-orm/node-postgres";
|
||||||
|
|
||||||
import * as schema from "./schema";
|
import * as schema from "./schema";
|
||||||
|
|
||||||
export const db = drizzle({
|
export const db = drizzle({
|
||||||
connection: `postgres://postgres:${DATABASE_PASSWORD}@database/kredens`,
|
connection: DATABASE_URL,
|
||||||
casing: "snake_case",
|
casing: "snake_case",
|
||||||
schema,
|
schema,
|
||||||
});
|
});
|
||||||
|
@ -1,15 +1,30 @@
|
|||||||
import { integer, pgTable, varchar, text, timestamp } from "drizzle-orm/pg-core";
|
import { relations } from "drizzle-orm";
|
||||||
|
import {
|
||||||
|
integer,
|
||||||
|
pgTable,
|
||||||
|
varchar,
|
||||||
|
text,
|
||||||
|
timestamp,
|
||||||
|
} from "drizzle-orm/pg-core";
|
||||||
|
|
||||||
export const users = pgTable("users", {
|
export const users = pgTable("users", {
|
||||||
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
||||||
username: varchar({length: 128, }).unique().notNull(),
|
username: varchar({ length: 128 }).unique().notNull(),
|
||||||
password_hash: text(),
|
passwordHash: text(),
|
||||||
displayName: text(),
|
displayName: text(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const sessions = pgTable("sessions", {
|
export const sessions = pgTable("sessions", {
|
||||||
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
id: text().primaryKey(),
|
||||||
userId: integer().references(() => users.id),
|
userId: integer()
|
||||||
|
.notNull()
|
||||||
|
.references(() => users.id),
|
||||||
expiresAt: timestamp({ withTimezone: true }).notNull(),
|
expiresAt: timestamp({ withTimezone: true }).notNull(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const sessionRelations = relations(sessions, ({ one }) => ({
|
||||||
|
user: one(users, {
|
||||||
|
fields: [ sessions.userId ],
|
||||||
|
references: [users.id],
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
@ -1,5 +1,123 @@
|
|||||||
|
import {
|
||||||
|
encodeBase32LowerCaseNoPadding,
|
||||||
|
encodeHexLowerCase,
|
||||||
|
} from "@oslojs/encoding";
|
||||||
|
import { sha256 } from "@oslojs/crypto/sha2";
|
||||||
|
import type { RequestEvent } from "@sveltejs/kit";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { Temporal, toTemporalInstant } from "@js-temporal/polyfill";
|
||||||
|
|
||||||
|
import { db } from "./db";
|
||||||
|
import { users, sessions } from "./schema";
|
||||||
|
import type { User } from "./user";
|
||||||
|
|
||||||
|
const sessionExpirySeconds = 30 * 86400;
|
||||||
|
const sessionExpiry = new Temporal.Duration(0, 0, 0, 0, 0, 0, sessionExpirySeconds);
|
||||||
|
const sessionRenewal = new Temporal.Duration(0, 0, 0, 0, 0, 0, sessionExpirySeconds/2);
|
||||||
|
|
||||||
|
export async function validateSessionToken(
|
||||||
|
token: string,
|
||||||
|
): Promise<SessionValidationResult> {
|
||||||
|
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
|
||||||
|
|
||||||
|
const row = await db.query.sessions.findFirst({
|
||||||
|
with: {
|
||||||
|
user: true,
|
||||||
|
},
|
||||||
|
where: (sessions, { eq }) => eq(sessions.id, sessionId),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!row) {
|
||||||
|
return { session: null, user: null };
|
||||||
|
}
|
||||||
|
const session: Session = {
|
||||||
|
id: row.id,
|
||||||
|
userId: row.userId,
|
||||||
|
expiresAt: row.expiresAt,
|
||||||
|
};
|
||||||
|
|
||||||
|
const user: User = {
|
||||||
|
id: row.userId,
|
||||||
|
username: row.user.username,
|
||||||
|
displayName: row.user.displayName || undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const now = Temporal.Now.instant();
|
||||||
|
const expiresAt = toTemporalInstant.apply(session.expiresAt);
|
||||||
|
|
||||||
|
if (Temporal.Instant.compare(now, expiresAt) > 0) {
|
||||||
|
await db.delete(sessions).where(eq(sessions.id, session.id));
|
||||||
|
return { session: null, user: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Temporal.Instant.compare(now.add(sessionRenewal), expiresAt) > 0) {
|
||||||
|
session.expiresAt = new Date(now.add(sessionExpiry).epochMilliseconds);
|
||||||
|
await db.update(sessions)
|
||||||
|
.set({ expiresAt: session.expiresAt })
|
||||||
|
.where(eq(sessions.id, session.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
return { session, user };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function invalidateSession(sessionId: string): Promise<void> {
|
||||||
|
await db.delete(sessions).where(eq(sessions.id, sessionId));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function invalidateUserSessions(userId: number): Promise<void> {
|
||||||
|
await db.delete(sessions).where(eq(sessions.userId, userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setSessionTokenCookie(
|
||||||
|
event: RequestEvent,
|
||||||
|
token: string,
|
||||||
|
expiresAt: Date,
|
||||||
|
): void {
|
||||||
|
event.cookies.set("session", token, {
|
||||||
|
httpOnly: true,
|
||||||
|
path: "/",
|
||||||
|
secure: import.meta.env.PROD,
|
||||||
|
sameSite: "lax",
|
||||||
|
expires: expiresAt,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteSessionTokenCookie(event: RequestEvent): void {
|
||||||
|
event.cookies.set("session", "", {
|
||||||
|
httpOnly: true,
|
||||||
|
path: "/",
|
||||||
|
secure: import.meta.env.PROD,
|
||||||
|
sameSite: "lax",
|
||||||
|
maxAge: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateSessionToken(): string {
|
||||||
|
const tokenBytes = new Uint8Array(20);
|
||||||
|
crypto.getRandomValues(tokenBytes);
|
||||||
|
const token = encodeBase32LowerCaseNoPadding(tokenBytes).toLowerCase();
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createSession(token: string, userId: number): Promise<Session> {
|
||||||
|
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
|
||||||
|
const session: Session = {
|
||||||
|
id: sessionId,
|
||||||
|
userId,
|
||||||
|
expiresAt: new Date(
|
||||||
|
Temporal.Now.instant().add(sessionExpiry).epochMilliseconds,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
await db.insert(sessions).values(session);
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Session {
|
export interface Session {
|
||||||
id: string;
|
id: string;
|
||||||
expiresAt: Date;
|
expiresAt: Date;
|
||||||
userId: number;
|
userId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SessionValidationResult =
|
||||||
|
| { session: Session; user: User }
|
||||||
|
| { session: null; user: null };
|
||||||
|
@ -1,5 +1,45 @@
|
|||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { db } from "./db";
|
||||||
|
import { users } from "./schema";
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
id: number;
|
id: number;
|
||||||
username: string;
|
username: string;
|
||||||
displayName: string;
|
displayName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function getUserFromUsername(username: string): Promise<User | null> {
|
||||||
|
const user = await db.query.users.findFirst({
|
||||||
|
columns: {
|
||||||
|
id: true,
|
||||||
|
username: true,
|
||||||
|
displayName: true
|
||||||
|
},
|
||||||
|
where: eq(users.username, username)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...user,
|
||||||
|
displayName: user.displayName || undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUserPasswordHash(userId: number): Promise<string|null> {
|
||||||
|
const user = (await db.query.users.findFirst({
|
||||||
|
columns: {
|
||||||
|
passwordHash: true,
|
||||||
|
},
|
||||||
|
where: eq(users.id, userId)
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (user === undefined) {
|
||||||
|
throw new Error("Invalid user ID");
|
||||||
|
}
|
||||||
|
|
||||||
|
return user.passwordHash;
|
||||||
}
|
}
|
7
src/routes/+layout.server.ts
Normal file
7
src/routes/+layout.server.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import type { LayoutServerLoad } from "./$types";
|
||||||
|
|
||||||
|
export const load: LayoutServerLoad = async (event) => {
|
||||||
|
return {
|
||||||
|
username: event.locals.user?.displayName || event.locals.user?.username,
|
||||||
|
};
|
||||||
|
};
|
16
src/routes/+layout.svelte
Normal file
16
src/routes/+layout.svelte
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
const { data, children } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<nav>
|
||||||
|
{#if data.username}
|
||||||
|
<div>
|
||||||
|
<p>Logged in as {data.username}.</p>
|
||||||
|
<form method="POST" action="/logout">
|
||||||
|
<button>Log out</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{@render children()}
|
@ -1,12 +0,0 @@
|
|||||||
import { count } from "drizzle-orm";
|
|
||||||
|
|
||||||
import { db } from "$lib/server/db";
|
|
||||||
import { users } from "$lib/server/schema";
|
|
||||||
|
|
||||||
import type { PageServerLoad } from "./$types";
|
|
||||||
|
|
||||||
export const load: PageServerLoad = async () => {
|
|
||||||
return {
|
|
||||||
c: (await db.select({ value: count() }).from(users))[0]
|
|
||||||
};
|
|
||||||
};
|
|
@ -6,5 +6,3 @@
|
|||||||
|
|
||||||
<h1>Welcome to SvelteKit</h1>
|
<h1>Welcome to SvelteKit</h1>
|
||||||
<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>
|
<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>
|
||||||
|
|
||||||
<p>There is { data.c.value } users!</p>
|
|
@ -1,18 +1,87 @@
|
|||||||
import { redirect } from "@sveltejs/kit";
|
import { fail, redirect } from "@sveltejs/kit";
|
||||||
import { RefillingTokenBucket, Throttler } from "$lib/server/rate-limit";
|
|
||||||
|
|
||||||
import type { Actions, PageServerLoad, PageServerLoadEvent } from "./$types";
|
|
||||||
|
import { RefillingTokenBucket, Throttler } from "$lib/server/rate-limit";
|
||||||
|
import type { Actions, PageServerLoad, PageServerLoadEvent, RequestEvent } from "./$types";
|
||||||
|
import { getUserFromUsername, getUserPasswordHash } from "$lib/server/user";
|
||||||
|
import { verifyPasswordHash } from "$lib/server/password";
|
||||||
|
import { createSession, generateSessionToken, setSessionTokenCookie } from "$lib/server/session";
|
||||||
|
|
||||||
const throttler = new Throttler<number>([0, 1, 2, 4, 8, 16, 30, 60, 180, 300]);
|
const throttler = new Throttler<number>([0, 1, 2, 4, 8, 16, 30, 60, 180, 300]);
|
||||||
const ipBucket = new RefillingTokenBucket<string>(20, 1);
|
const ipBucket = new RefillingTokenBucket<string>(20, 1);
|
||||||
|
|
||||||
export const load: PageServerLoad = (event: PageServerLoadEvent) => {
|
export const load: PageServerLoad = (event: PageServerLoadEvent) => {
|
||||||
if (event.locals.session !== null && event.locals.user !== null) {
|
if (event.locals.session !== null && event.locals.user !== null) {
|
||||||
return redirect(302, "/");
|
redirect(303, "/");
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actions = {
|
export const actions: Actions = {
|
||||||
default: async (event) => {},
|
default: action
|
||||||
} satisfies Actions;
|
};
|
||||||
|
|
||||||
|
async function action(event: RequestEvent) {
|
||||||
|
// TODO: Assumes X-Forwarded-For is always included.
|
||||||
|
const clientIP = event.request.headers.get("X-Forwarded-For");
|
||||||
|
if (clientIP !== null && !ipBucket.check(clientIP, 1)) {
|
||||||
|
return fail(429, {
|
||||||
|
message: "Too many requests",
|
||||||
|
email: ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = await event.request.formData();
|
||||||
|
const username = formData.get("username");
|
||||||
|
const password = formData.get("password");
|
||||||
|
if (typeof username !== "string" || typeof password !== "string") {
|
||||||
|
return fail(400, {
|
||||||
|
message: "Invalid or missing fields",
|
||||||
|
username: ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (username === "" || password === "") {
|
||||||
|
return fail(400, {
|
||||||
|
message: "Please enter your username and password.",
|
||||||
|
username: username
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const user = await getUserFromUsername(username);
|
||||||
|
if (user === null) {
|
||||||
|
return fail(400, {
|
||||||
|
message: "Account does not exist",
|
||||||
|
username: username
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (clientIP !== null && !ipBucket.consume(clientIP, 1)) {
|
||||||
|
return fail(429, {
|
||||||
|
message: "Too many requests",
|
||||||
|
username: ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!throttler.consume(user.id)) {
|
||||||
|
return fail(429, {
|
||||||
|
message: "Too many requests",
|
||||||
|
username: ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const passwordHash = await getUserPasswordHash(user.id);
|
||||||
|
if (passwordHash === null) {
|
||||||
|
return fail(400, {
|
||||||
|
message: "Account locked",
|
||||||
|
username
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const validPassword = await verifyPasswordHash(passwordHash, password);
|
||||||
|
if (!validPassword) {
|
||||||
|
return fail(400, {
|
||||||
|
message: "Invalid password",
|
||||||
|
username
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throttler.reset(user.id);
|
||||||
|
const sessionToken = generateSessionToken();
|
||||||
|
const session = await createSession(sessionToken, user.id);
|
||||||
|
setSessionTokenCookie(event, sessionToken, session.expiresAt);
|
||||||
|
return redirect(303, "/");
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { enhance } from "$app/forms";
|
||||||
|
|
||||||
|
import type { PageProps } from "./$types";
|
||||||
|
|
||||||
|
const { data, form }: PageProps = $props();
|
||||||
|
</script>
|
||||||
|
<form method="post" use:enhance>
|
||||||
|
<label for="form-login.username">Username</label>
|
||||||
|
<input
|
||||||
|
id="form-login.username"
|
||||||
|
name="username"
|
||||||
|
autocomplete="username"
|
||||||
|
required
|
||||||
|
value={form?.username || ""}
|
||||||
|
/><br />
|
||||||
|
<label for="form-login.password">Password</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="form-login.password"
|
||||||
|
name="password"
|
||||||
|
autocomplete="current-password"
|
||||||
|
required
|
||||||
|
/><br />
|
||||||
|
<button>Continue</button>
|
||||||
|
<p>{form?.message ?? ""}</p>
|
||||||
|
</form>
|
28
src/routes/logout/+page.server.ts
Normal file
28
src/routes/logout/+page.server.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { fail, redirect, type Actions } from "@sveltejs/kit";
|
||||||
|
|
||||||
|
import type { PageServerLoad } from "./$types";
|
||||||
|
import {
|
||||||
|
deleteSessionTokenCookie,
|
||||||
|
invalidateSession,
|
||||||
|
} from "$lib/server/session";
|
||||||
|
|
||||||
|
export const load: PageServerLoad = (event) => {
|
||||||
|
if (event.locals.session !== null && event.locals.user !== null) {
|
||||||
|
return redirect(302, "/");
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
default: async (event) => {
|
||||||
|
if (event.locals.session === null) {
|
||||||
|
return fail(401, {
|
||||||
|
message: "Not authenticated",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await invalidateSession(event.locals.session.id);
|
||||||
|
deleteSessionTokenCookie(event);
|
||||||
|
return redirect(303, "/login");
|
||||||
|
},
|
||||||
|
} satisfies Actions;
|
@ -1,4 +1,4 @@
|
|||||||
import adapter from "@sveltejs/adapter-auto";
|
import adapter from "@sveltejs/adapter-node";
|
||||||
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
|
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
|
||||||
|
|
||||||
/** @type {import('@sveltejs/kit').Config} */
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
@ -13,6 +13,9 @@ const config = {
|
|||||||
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
||||||
adapter: adapter(),
|
adapter: adapter(),
|
||||||
},
|
},
|
||||||
|
compilerOptions: {
|
||||||
|
runes: true,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
@ -6,5 +6,5 @@ export default defineConfig({
|
|||||||
|
|
||||||
test: {
|
test: {
|
||||||
include: ["src/**/*.{test,spec}.{js,ts}"],
|
include: ["src/**/*.{test,spec}.{js,ts}"],
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user