From 3e80d8636869dd9b5c68f1184f321dac0b7f14d6 Mon Sep 17 00:00:00 2001
From: steel <mael.acier@ensiie.fr>
Date: Mon, 5 Feb 2024 16:03:09 +0100
Subject: [PATCH] 2.0: Lucia

---
 package.json                       |   6 +-
 pnpm-lock.yaml                     | 579 ++++++++++++++++++++++++++++-
 src/auth.ts                        |  10 +
 src/hooks.server.ts                |   3 +
 src/lib/cookies.ts                 |  58 ---
 src/lib/default_lucia.ts           |  65 ++++
 src/lib/handlers/cookie.ts         |  32 --
 src/lib/handlers/index.ts          |  35 --
 src/lib/handlers/login.ts          |  30 --
 src/lib/handlers/loginCallback.ts  |  53 ---
 src/lib/handlers/logout.ts         |  19 -
 src/lib/handlers/logoutCallback.ts |  15 -
 src/lib/helpers.ts                 |  48 +++
 src/lib/index.ts                   | 250 +++++++++++--
 src/lib/lucia.ts                   |  65 ++++
 src/lib/paths.ts                   |   5 -
 src/lib/public.ts                  |   5 -
 src/lib/types.ts                   |  70 ++--
 src/lib/utils/jwt_cookie.ts        |  77 ----
 src/routes/+layout.server.ts       |   5 +
 src/routes/+page.server.ts         |  10 +
 src/routes/+page.svelte            |   9 +
 src/routes/login/+page.server.ts   |   8 +
 src/routes/login/+page.svelte      |   6 +
 static/favicon.png                 | Bin 0 -> 1571 bytes
 25 files changed, 1041 insertions(+), 422 deletions(-)
 create mode 100644 src/auth.ts
 create mode 100644 src/hooks.server.ts
 delete mode 100644 src/lib/cookies.ts
 create mode 100644 src/lib/default_lucia.ts
 delete mode 100644 src/lib/handlers/cookie.ts
 delete mode 100644 src/lib/handlers/index.ts
 delete mode 100644 src/lib/handlers/login.ts
 delete mode 100644 src/lib/handlers/loginCallback.ts
 delete mode 100644 src/lib/handlers/logout.ts
 delete mode 100644 src/lib/handlers/logoutCallback.ts
 create mode 100644 src/lib/helpers.ts
 create mode 100644 src/lib/lucia.ts
 delete mode 100644 src/lib/paths.ts
 delete mode 100644 src/lib/public.ts
 delete mode 100644 src/lib/utils/jwt_cookie.ts
 create mode 100644 src/routes/+layout.server.ts
 create mode 100644 src/routes/+page.server.ts
 create mode 100644 src/routes/+page.svelte
 create mode 100644 src/routes/login/+page.server.ts
 create mode 100644 src/routes/login/+page.svelte
 create mode 100644 static/favicon.png

diff --git a/package.json b/package.json
index 97742d7..216e58b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@arise/aidc-sveltekit",
-  "version": "0.1.1",
+  "version": "0.2.0",
   "type": "module",
   "scripts": {
     "dev": "vite dev",
@@ -39,6 +39,7 @@
     "@sveltejs/kit": "^2.0.0",
     "@sveltejs/package": "^2.0.0",
     "@sveltejs/vite-plugin-svelte": "^3.0.0",
+    "@types/better-sqlite3": "^7.6.9",
     "@types/cookie": "^0.6.0",
     "@types/jsonwebtoken": "^9.0.5",
     "@typescript-eslint/eslint-plugin": "^6.14.0",
@@ -57,9 +58,12 @@
     "vitest": "^1.0.0"
   },
   "dependencies": {
+    "@lucia-auth/adapter-sqlite": "^3.0.0",
     "assert": "^2.1.0",
+    "better-sqlite3": "^9.4.0",
     "cookie": "^0.6.0",
     "jsonwebtoken": "^9.0.2",
+    "lucia": "^3.0.1",
     "openid-client": "^5.6.1",
     "readable-http-codes": "^1.1.1",
     "zod": "^3.22.4"
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index dc91ec1..d5e388c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -5,15 +5,24 @@ settings:
   excludeLinksFromLockfile: false
 
 dependencies:
+  '@lucia-auth/adapter-sqlite':
+    specifier: ^3.0.0
+    version: 3.0.0(better-sqlite3@9.4.0)(lucia@3.0.1)
   assert:
     specifier: ^2.1.0
     version: 2.1.0
+  better-sqlite3:
+    specifier: ^9.4.0
+    version: 9.4.0
   cookie:
     specifier: ^0.6.0
     version: 0.6.0
   jsonwebtoken:
     specifier: ^9.0.2
     version: 9.0.2
+  lucia:
+    specifier: ^3.0.1
+    version: 3.0.1
   openid-client:
     specifier: ^5.6.1
     version: 5.6.1
@@ -34,6 +43,9 @@ devDependencies:
   '@sveltejs/vite-plugin-svelte':
     specifier: ^3.0.0
     version: 3.0.1(svelte@4.2.8)(vite@5.0.9)
+  '@types/better-sqlite3':
+    specifier: ^7.6.9
+    version: 7.6.9
   '@types/cookie':
     specifier: ^0.6.0
     version: 0.6.0
@@ -98,6 +110,22 @@ packages:
       '@jridgewell/trace-mapping': 0.3.20
     dev: true
 
+  /@emnapi/core@0.45.0:
+    resolution: {integrity: sha512-DPWjcUDQkCeEM4VnljEOEcXdAD7pp8zSZsgOujk/LGIwCXWbXJngin+MO4zbH429lzeC3WbYLGjE2MaUOwzpyw==}
+    requiresBuild: true
+    dependencies:
+      tslib: 2.6.2
+    dev: false
+    optional: true
+
+  /@emnapi/runtime@0.45.0:
+    resolution: {integrity: sha512-Txumi3td7J4A/xTTwlssKieHKTGl3j4A1tglBx72auZ49YK7ePY6XZricgIg9mnZT4xPfA+UPCUdnhRuEFDL+w==}
+    requiresBuild: true
+    dependencies:
+      tslib: 2.6.2
+    dev: false
+    optional: true
+
   /@esbuild/android-arm64@0.19.9:
     resolution: {integrity: sha512-q4cR+6ZD0938R19MyEW3jEsMzbb/1rulLXiNAJQADD/XYp7pT+rOS5JGxvpRW8dFDEfjW4wLgC/3FXIw4zYglQ==}
     engines: {node: '>=12'}
@@ -390,6 +418,326 @@ packages:
       '@jridgewell/sourcemap-codec': 1.4.15
     dev: true
 
+  /@lucia-auth/adapter-sqlite@3.0.0(better-sqlite3@9.4.0)(lucia@3.0.1):
+    resolution: {integrity: sha512-7XMfMMNziFOoqIq2u9W5Z+wo7G7lzNQfUh7wDlkIqKxJMSLtP6GKH+QpK+21X4ZY9dIDmiuiwqZmS3Q3CgpATw==}
+    peerDependencies:
+      '@libsql/client': ^0.3.0
+      better-sqlite3: 8.x - 9.x
+      lucia: 3.x
+    peerDependenciesMeta:
+      '@libsql/client':
+        optional: true
+      better-sqlite3:
+        optional: true
+    dependencies:
+      better-sqlite3: 9.4.0
+      lucia: 3.0.1
+    dev: false
+
+  /@napi-rs/wasm-runtime@0.1.1:
+    resolution: {integrity: sha512-ATj9ua659JgrkICjJscaeZdmPr44cb/KFjNWuD0N6pux0SpzaM7+iOuuK11mAnQM2N9q0DT4REu6NkL8ZEhopw==}
+    requiresBuild: true
+    dependencies:
+      '@emnapi/core': 0.45.0
+      '@emnapi/runtime': 0.45.0
+      '@tybys/wasm-util': 0.8.1
+    dev: false
+    optional: true
+
+  /@node-rs/argon2-android-arm-eabi@1.7.2:
+    resolution: {integrity: sha512-WhW84XOzdR4AOGc4BJvIg5lCRVBL0pXp/PPCe8QCyWw493p7VdNCdYpr2xdtjS/0zImmY85HNB/6zpzjLRTT/A==}
+    engines: {node: '>= 10'}
+    cpu: [arm]
+    os: [android]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@node-rs/argon2-android-arm64@1.7.2:
+    resolution: {integrity: sha512-CdtayHSMIyDuVhSYFirwA757c4foQuyTjpysgFJLHweP9C7uDiBf9WBYij+UyabpaCadJ0wPyK6Vakinvlk4/g==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [android]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@node-rs/argon2-darwin-arm64@1.7.2:
+    resolution: {integrity: sha512-hUOhtgYHTEyzX5sgMZVdXunONOus2HWpWydF5D/RYJ1mZ76FXRnFpQE40DqbzisdPIraKdn40m7JqkPP7wqdyg==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [darwin]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@node-rs/argon2-darwin-x64@1.7.2:
+    resolution: {integrity: sha512-lfs5HX+t542yUfcv6Aa/NeGD1nUCwyQNgnPEGcik71Ow6V13hkR1bHgmT1u3CHN4fBts0gW+DQEDsq1xlVgkvw==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [darwin]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@node-rs/argon2-freebsd-x64@1.7.2:
+    resolution: {integrity: sha512-ROoF+4VaCBJUjddrTN1hjuqSl89ppRcjVXJscSPJjWzTlbzFmGGovJvIzUBmCr/Oq3yM1zKHj6MP9oRD5cB+/g==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [freebsd]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@node-rs/argon2-linux-arm-gnueabihf@1.7.2:
+    resolution: {integrity: sha512-CBSB8KPI8LS74Bcz3dYaa2/khULutz4vSDvFWUERlSLX+mPdDhoZi6UPuUPPF9e01w8AbiK1YCqlLUTm3tIMfw==}
+    engines: {node: '>= 10'}
+    cpu: [arm]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@node-rs/argon2-linux-arm64-gnu@1.7.2:
+    resolution: {integrity: sha512-6LBTug6ZiWFakP3X3Nqs7ZTM03gmcSWX4YvEn20HhhQE5NDrsrw3zNqGj0cJiNzKKIMSDDuj7uGy+ITEfNo4CA==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@node-rs/argon2-linux-arm64-musl@1.7.2:
+    resolution: {integrity: sha512-KjhQ+ZPne29t9VRVeIif7JdKwQba+tM6CBNYBoJB1iON0CUKeqSQtZcHuTj9gkf2SNRG5bsU4ABcfxd0OKsKHg==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@node-rs/argon2-linux-x64-gnu@1.7.2:
+    resolution: {integrity: sha512-BQvp+iLtKqomHz4q5t1aKoni9osgvUDU5sZtHAlFm5dRTlGHnympcQVATRE5GHyH9C6MIM9W7P1kqEeCLGPolQ==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@node-rs/argon2-linux-x64-musl@1.7.2:
+    resolution: {integrity: sha512-yXJudpBZQ98g+lWaHn9EzZ5KsAyqRdlpub/K+5NP7gHehb8wzBRIFAejIHAG0fvzQEEc86VOnV2koWIVZxWAvw==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@node-rs/argon2-wasm32-wasi@1.7.2:
+    resolution: {integrity: sha512-diXlVjJZY2GIV8ZDwUqXPhacXsFR0klGSv5D9f+XidwWXK4udtzDhkM/7N/Mb7h1HAWaxZ6IN9spYFjvWH1wqg==}
+    engines: {node: '>=14.0.0'}
+    cpu: [wasm32]
+    requiresBuild: true
+    dependencies:
+      '@napi-rs/wasm-runtime': 0.1.1
+    dev: false
+    optional: true
+
+  /@node-rs/argon2-win32-arm64-msvc@1.7.2:
+    resolution: {integrity: sha512-dhIBrY04P9nbmwzBpgERQDmmSu4YBZyeEE32t4TikMz5rQ07iaVC+JpGmtCBZoDIsLDHGC8cikENd3YEqpqIcA==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [win32]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@node-rs/argon2-win32-ia32-msvc@1.7.2:
+    resolution: {integrity: sha512-o1tfqr8gyALCzuxBoQfvhxkeYMaw/0H8Gmt7klTYyEIBvEFu7SD5qytXO9Px7t5420nZL/Wy5cflg3IB1s57Pg==}
+    engines: {node: '>= 10'}
+    cpu: [ia32]
+    os: [win32]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@node-rs/argon2-win32-x64-msvc@1.7.2:
+    resolution: {integrity: sha512-v0h53XUc7hNgWiWi0qcMcHvj9/kwuItI9NwLK4C+gtzT3UB0cedhfIL8HFMKThMXasy41ZdbpCF2Bi0kJoLNEg==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [win32]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@node-rs/argon2@1.7.2:
+    resolution: {integrity: sha512-+H6pc3M1vIX9YnG59YW7prHhhpv19P8YyxlXHnnFzTimf2q+kKDF7mGWbhvN9STqIY+P70Patn0Q6qb6Ib5/4g==}
+    engines: {node: '>= 10'}
+    optionalDependencies:
+      '@node-rs/argon2-android-arm-eabi': 1.7.2
+      '@node-rs/argon2-android-arm64': 1.7.2
+      '@node-rs/argon2-darwin-arm64': 1.7.2
+      '@node-rs/argon2-darwin-x64': 1.7.2
+      '@node-rs/argon2-freebsd-x64': 1.7.2
+      '@node-rs/argon2-linux-arm-gnueabihf': 1.7.2
+      '@node-rs/argon2-linux-arm64-gnu': 1.7.2
+      '@node-rs/argon2-linux-arm64-musl': 1.7.2
+      '@node-rs/argon2-linux-x64-gnu': 1.7.2
+      '@node-rs/argon2-linux-x64-musl': 1.7.2
+      '@node-rs/argon2-wasm32-wasi': 1.7.2
+      '@node-rs/argon2-win32-arm64-msvc': 1.7.2
+      '@node-rs/argon2-win32-ia32-msvc': 1.7.2
+      '@node-rs/argon2-win32-x64-msvc': 1.7.2
+    dev: false
+
+  /@node-rs/bcrypt-android-arm-eabi@1.9.2:
+    resolution: {integrity: sha512-er/Q2khwpan9pczvTTqY/DJE4UU65u31xd0NkZlHUTKyB7djRhWfzoGexGx2GN+k831/RR3U8kKE/8QUHeO3hQ==}
+    engines: {node: '>= 10'}
+    cpu: [arm]
+    os: [android]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@node-rs/bcrypt-android-arm64@1.9.2:
+    resolution: {integrity: sha512-OUYatOEG5vbLbF73q2TC8UqrDO81zUQxnaFD/OAB1hcm6J+ur0zJ8E53c35/DIqkTp7JarPMraC4rouJ2ugN4w==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [android]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@node-rs/bcrypt-darwin-arm64@1.9.2:
+    resolution: {integrity: sha512-svJKsGbzMAxOB5oluOYneN4YkKUy26WSMgm3KOIhgoX30IeMilj+2jFN/5qrI0oDZ0Iczb3XyL5DuZFtEkdP8A==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [darwin]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@node-rs/bcrypt-darwin-x64@1.9.2:
+    resolution: {integrity: sha512-9OrySjBi/rWix8NZWD/TrNbNcwMY0pAiMHdL09aJnJ07uPih83GGh1pq4UHCYFCMy7iTX8swOmDlGBUImkOZbg==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [darwin]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@node-rs/bcrypt-freebsd-x64@1.9.2:
+    resolution: {integrity: sha512-/djXV71RO6g5L1mI2pVvmp3x3pH7G4uKI3ODG1JBIXoz334oOcCMh40sB0uq0ljP8WEadker01p4T1rJE98fpg==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [freebsd]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@node-rs/bcrypt-linux-arm-gnueabihf@1.9.2:
+    resolution: {integrity: sha512-F7wP950OTAooxEleUN4I2hqryGZK7hi1cSgRF13Wvbc597RFux35KiSxIXUA3mNt2DE7lV2PeceEtCOScaThWQ==}
+    engines: {node: '>= 10'}
+    cpu: [arm]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@node-rs/bcrypt-linux-arm64-gnu@1.9.2:
+    resolution: {integrity: sha512-MehG+yQ0TgKMgKR1rO4hdvHkVsTM91Cof8qI9EJlS5+7+QSwfFA5O0zGwCkISD7bsyauJ5uJgcByGjpEobAHOg==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@node-rs/bcrypt-linux-arm64-musl@1.9.2:
+    resolution: {integrity: sha512-PRZTAJjOwKEGsIhmBvfNh81So+wGl4QyCFAt23j+KwBujLStjC0N3YaqtTlWVKG9tcriPtmMYiAQtXWIyIgg/w==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@node-rs/bcrypt-linux-x64-gnu@1.9.2:
+    resolution: {integrity: sha512-5WfGO+O1m7nJ55WZ8XDq+ItA98Z4O7sNWsR+1nIj9YGT+Tx5zkQ2RBhpK6oCWZMluuZ0eKQ0FDmyP6K+2NDRIA==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@node-rs/bcrypt-linux-x64-musl@1.9.2:
+    resolution: {integrity: sha512-VjCn0388p6PMCVUYHgYmHZrKNc7WwNJRr2WLJsHbQRGDOKbpNL6YolCjQxUchcSPDhzwrq1cIdy4j0fpoXEsdw==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@node-rs/bcrypt-wasm32-wasi@1.9.2:
+    resolution: {integrity: sha512-P06aHfMzm9makwU+nM7WA65yQnS1xuqJ8l/6I/LvXjnl+lfB3DtJ2B0CSLtjnUGpUgcHbWl5gEbNnTPxSAirjQ==}
+    engines: {node: '>=14.0.0'}
+    cpu: [wasm32]
+    requiresBuild: true
+    dependencies:
+      '@napi-rs/wasm-runtime': 0.1.1
+    dev: false
+    optional: true
+
+  /@node-rs/bcrypt-win32-arm64-msvc@1.9.2:
+    resolution: {integrity: sha512-Iyo/Q5/eNw27VRd3mLBgh1b9b5fnT3QHTVwxv3Siv/MRAIfJXH/cTOe18qSwYQzNh0ZioW4yemFPYCWSZi7szA==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [win32]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@node-rs/bcrypt-win32-ia32-msvc@1.9.2:
+    resolution: {integrity: sha512-6LHWMaPylyyHoS5863YpxAACVB8DWCxro5W6pQ4h8WKSgHpJp8Um9jphTdN0A2w45HZjUnfcFuiFFC+TbftjCw==}
+    engines: {node: '>= 10'}
+    cpu: [ia32]
+    os: [win32]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@node-rs/bcrypt-win32-x64-msvc@1.9.2:
+    resolution: {integrity: sha512-vZ9T1MOaYkLO9FTyl28YX0SYJneiYTKNFgM8PUv8nas8xrD+7OzokA0fEtlNp6413T7IKSD/iG9qi8nTWsiyGg==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [win32]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@node-rs/bcrypt@1.9.2:
+    resolution: {integrity: sha512-FKUo9iCSIti+ldwoOlY1ztyIFhZxEgT7jZ/UCt/9bg1rLmNdbQQD2JKIMImDCqmTWuLPY4ZF4Q5MyOMIfDCd8Q==}
+    engines: {node: '>= 10'}
+    optionalDependencies:
+      '@node-rs/bcrypt-android-arm-eabi': 1.9.2
+      '@node-rs/bcrypt-android-arm64': 1.9.2
+      '@node-rs/bcrypt-darwin-arm64': 1.9.2
+      '@node-rs/bcrypt-darwin-x64': 1.9.2
+      '@node-rs/bcrypt-freebsd-x64': 1.9.2
+      '@node-rs/bcrypt-linux-arm-gnueabihf': 1.9.2
+      '@node-rs/bcrypt-linux-arm64-gnu': 1.9.2
+      '@node-rs/bcrypt-linux-arm64-musl': 1.9.2
+      '@node-rs/bcrypt-linux-x64-gnu': 1.9.2
+      '@node-rs/bcrypt-linux-x64-musl': 1.9.2
+      '@node-rs/bcrypt-wasm32-wasi': 1.9.2
+      '@node-rs/bcrypt-win32-arm64-msvc': 1.9.2
+      '@node-rs/bcrypt-win32-ia32-msvc': 1.9.2
+      '@node-rs/bcrypt-win32-x64-msvc': 1.9.2
+    dev: false
+
   /@nodelib/fs.scandir@2.1.5:
     resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
     engines: {node: '>= 8'}
@@ -602,6 +950,20 @@ packages:
       - supports-color
     dev: true
 
+  /@tybys/wasm-util@0.8.1:
+    resolution: {integrity: sha512-GSsTwyBl4pIzsxAY5wroZdyQKyhXk0d8PCRZtrSZ2WEB1cBdrp2EgGBwHOGCZtIIPun/DL3+AykCv+J6fyRH4Q==}
+    requiresBuild: true
+    dependencies:
+      tslib: 2.6.2
+    dev: false
+    optional: true
+
+  /@types/better-sqlite3@7.6.9:
+    resolution: {integrity: sha512-FvktcujPDj9XKMJQWFcl2vVl7OdRIqsSRX9b0acWwTmwLK9CF2eqo/FRcmMLNpugKoX/avA6pb7TorDLmpgTnQ==}
+    dependencies:
+      '@types/node': 20.10.4
+    dev: true
+
   /@types/cookie@0.6.0:
     resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
     dev: true
@@ -904,11 +1266,37 @@ packages:
     resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
     dev: true
 
+  /base64-js@1.5.1:
+    resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
+    dev: false
+
+  /better-sqlite3@9.4.0:
+    resolution: {integrity: sha512-5kynxekMxSjCMiFyUBLHggFcJkCmiZi6fUkiGz/B5GZOvdRWQJD0klqCx5/Y+bm2AKP7I/DHbSFx26AxjruWNg==}
+    requiresBuild: true
+    dependencies:
+      bindings: 1.5.0
+      prebuild-install: 7.1.1
+    dev: false
+
   /binary-extensions@2.2.0:
     resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
     engines: {node: '>=8'}
     dev: true
 
+  /bindings@1.5.0:
+    resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
+    dependencies:
+      file-uri-to-path: 1.0.0
+    dev: false
+
+  /bl@4.1.0:
+    resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
+    dependencies:
+      buffer: 5.7.1
+      inherits: 2.0.4
+      readable-stream: 3.6.2
+    dev: false
+
   /brace-expansion@1.1.11:
     resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
     dependencies:
@@ -937,6 +1325,13 @@ packages:
     resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
     dev: false
 
+  /buffer@5.7.1:
+    resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
+    dependencies:
+      base64-js: 1.5.1
+      ieee754: 1.2.1
+    dev: false
+
   /cac@6.7.14:
     resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
     engines: {node: '>=8'}
@@ -997,6 +1392,10 @@ packages:
       fsevents: 2.3.3
     dev: true
 
+  /chownr@1.1.4:
+    resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
+    dev: false
+
   /code-red@1.0.4:
     resolution: {integrity: sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==}
     dependencies:
@@ -1061,6 +1460,13 @@ packages:
       ms: 2.1.2
     dev: true
 
+  /decompress-response@6.0.0:
+    resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
+    engines: {node: '>=10'}
+    dependencies:
+      mimic-response: 3.1.0
+    dev: false
+
   /dedent-js@1.0.1:
     resolution: {integrity: sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ==}
     dev: true
@@ -1072,6 +1478,11 @@ packages:
       type-detect: 4.0.8
     dev: true
 
+  /deep-extend@0.6.0:
+    resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
+    engines: {node: '>=4.0.0'}
+    dev: false
+
   /deep-is@0.1.4:
     resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
     dev: true
@@ -1109,6 +1520,11 @@ packages:
     engines: {node: '>=8'}
     dev: true
 
+  /detect-libc@2.0.2:
+    resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==}
+    engines: {node: '>=8'}
+    dev: false
+
   /devalue@4.3.2:
     resolution: {integrity: sha512-KqFl6pOgOW+Y6wJgu80rHpo2/3H07vr8ntR9rkkFIRETewbf5GaYYcakYfiKz89K+sLsuPkQIZaXDMjUObZwWg==}
     dev: true
@@ -1138,6 +1554,12 @@ packages:
       safe-buffer: 5.2.1
     dev: false
 
+  /end-of-stream@1.4.4:
+    resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
+    dependencies:
+      once: 1.4.0
+    dev: false
+
   /es6-promise@3.3.1:
     resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==}
     dev: true
@@ -1342,6 +1764,11 @@ packages:
       strip-final-newline: 3.0.0
     dev: true
 
+  /expand-template@2.0.3:
+    resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
+    engines: {node: '>=6'}
+    dev: false
+
   /fast-deep-equal@3.1.3:
     resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
     dev: true
@@ -1378,6 +1805,10 @@ packages:
       flat-cache: 3.2.0
     dev: true
 
+  /file-uri-to-path@1.0.0:
+    resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==}
+    dev: false
+
   /fill-range@7.0.1:
     resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
     engines: {node: '>=8'}
@@ -1412,6 +1843,10 @@ packages:
       is-callable: 1.2.7
     dev: false
 
+  /fs-constants@1.0.0:
+    resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
+    dev: false
+
   /fs.realpath@1.0.0:
     resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
     dev: true
@@ -1446,6 +1881,10 @@ packages:
     engines: {node: '>=16'}
     dev: true
 
+  /github-from-package@0.0.0:
+    resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==}
+    dev: false
+
   /glob-parent@5.1.2:
     resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
     engines: {node: '>= 6'}
@@ -1563,6 +2002,10 @@ packages:
     engines: {node: '>=16.17.0'}
     dev: true
 
+  /ieee754@1.2.1:
+    resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
+    dev: false
+
   /ignore-walk@5.0.1:
     resolution: {integrity: sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw==}
     engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
@@ -1598,6 +2041,10 @@ packages:
   /inherits@2.0.4:
     resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
 
+  /ini@1.3.8:
+    resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
+    dev: false
+
   /is-arguments@1.1.1:
     resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==}
     engines: {node: '>= 0.4'}
@@ -1832,6 +2279,12 @@ packages:
     dependencies:
       yallist: 4.0.0
 
+  /lucia@3.0.1:
+    resolution: {integrity: sha512-srwUkTCGgr6N4mFpaKZVZy5kwiRZdsrbIDv9Wrjar+xyw1MjojYQQ7oRbegjRWOZ3yI8xOOclK3sz/rga2J7/w==}
+    dependencies:
+      oslo: 1.0.1
+    dev: false
+
   /magic-string@0.27.0:
     resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==}
     engines: {node: '>=12'}
@@ -1872,6 +2325,11 @@ packages:
     engines: {node: '>=12'}
     dev: true
 
+  /mimic-response@3.1.0:
+    resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
+    engines: {node: '>=10'}
+    dev: false
+
   /min-indent@1.0.1:
     resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
     engines: {node: '>=4'}
@@ -1892,7 +2350,10 @@ packages:
 
   /minimist@1.2.8:
     resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
-    dev: true
+
+  /mkdirp-classic@0.5.3:
+    resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
+    dev: false
 
   /mkdirp@0.5.6:
     resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
@@ -1929,6 +2390,10 @@ packages:
     hasBin: true
     dev: true
 
+  /napi-build-utils@1.0.2:
+    resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==}
+    dev: false
+
   /natural-compare@1.4.0:
     resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
     dev: true
@@ -1940,6 +2405,13 @@ packages:
       tslib: 2.6.2
     dev: true
 
+  /node-abi@3.54.0:
+    resolution: {integrity: sha512-p7eGEiQil0YUV3ItH4/tBb781L5impVmmx2E9FRKF7d18XXzp4PGT2tdYMFY6wQqgxD0IwNZOiSJ0/K0fSi/OA==}
+    engines: {node: '>=10'}
+    dependencies:
+      semver: 7.5.4
+    dev: false
+
   /normalize-path@3.0.0:
     resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
     engines: {node: '>=0.10.0'}
@@ -2012,7 +2484,6 @@ packages:
     resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
     dependencies:
       wrappy: 1.0.2
-    dev: true
 
   /onetime@6.0.0:
     resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
@@ -2042,6 +2513,13 @@ packages:
       type-check: 0.4.0
     dev: true
 
+  /oslo@1.0.1:
+    resolution: {integrity: sha512-esfzZry+HfGgK/GCYkg7BRlLd3RH5aHa08wgLJPYjENXybi0BvXxGk0LbUj+lXfz2TkjPDHe4rB/o6JxRLHxBg==}
+    dependencies:
+      '@node-rs/argon2': 1.7.2
+      '@node-rs/bcrypt': 1.9.2
+    dev: false
+
   /p-limit@3.1.0:
     resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
     engines: {node: '>=10'}
@@ -2187,6 +2665,25 @@ packages:
       source-map-js: 1.0.2
     dev: true
 
+  /prebuild-install@7.1.1:
+    resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==}
+    engines: {node: '>=10'}
+    hasBin: true
+    dependencies:
+      detect-libc: 2.0.2
+      expand-template: 2.0.3
+      github-from-package: 0.0.0
+      minimist: 1.2.8
+      mkdirp-classic: 0.5.3
+      napi-build-utils: 1.0.2
+      node-abi: 3.54.0
+      pump: 3.0.0
+      rc: 1.2.8
+      simple-get: 4.0.1
+      tar-fs: 2.1.1
+      tunnel-agent: 0.6.0
+    dev: false
+
   /prelude-ls@1.2.1:
     resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
     engines: {node: '>= 0.8.0'}
@@ -2227,6 +2724,13 @@ packages:
       sade: 1.8.1
     dev: true
 
+  /pump@3.0.0:
+    resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==}
+    dependencies:
+      end-of-stream: 1.4.4
+      once: 1.4.0
+    dev: false
+
   /punycode@2.3.1:
     resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
     engines: {node: '>=6'}
@@ -2236,6 +2740,16 @@ packages:
     resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
     dev: true
 
+  /rc@1.2.8:
+    resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
+    hasBin: true
+    dependencies:
+      deep-extend: 0.6.0
+      ini: 1.3.8
+      minimist: 1.2.8
+      strip-json-comments: 2.0.1
+    dev: false
+
   /react-is@18.2.0:
     resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==}
     dev: true
@@ -2244,6 +2758,15 @@ packages:
     resolution: {integrity: sha512-M+u7iokHWtqEXmeeLuPztJrWnS6kMvqDWEg/Ho7i1hqVv64ROJLqf0U0PHhwQJo9Q7lynj25sbDG8hljdLEfHg==}
     dev: false
 
+  /readable-stream@3.6.2:
+    resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
+    engines: {node: '>= 6'}
+    dependencies:
+      inherits: 2.0.4
+      string_decoder: 1.3.0
+      util-deprecate: 1.0.2
+    dev: false
+
   /readdirp@3.6.0:
     resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
     engines: {node: '>=8.10.0'}
@@ -2364,6 +2887,18 @@ packages:
     engines: {node: '>=14'}
     dev: true
 
+  /simple-concat@1.0.1:
+    resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==}
+    dev: false
+
+  /simple-get@4.0.1:
+    resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==}
+    dependencies:
+      decompress-response: 6.0.0
+      once: 1.4.0
+      simple-concat: 1.0.1
+    dev: false
+
   /sirv@2.0.3:
     resolution: {integrity: sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==}
     engines: {node: '>= 10'}
@@ -2401,6 +2936,12 @@ packages:
     resolution: {integrity: sha512-aFZ19IgVmhdB2uX599ve2kE6BIE3YMnQ6Gp6BURhW/oIzpXGKr878TQfAQZn1+i0Flcc/UKUy1gOlcfaUBCryg==}
     dev: true
 
+  /string_decoder@1.3.0:
+    resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
+    dependencies:
+      safe-buffer: 5.2.1
+    dev: false
+
   /strip-ansi@6.0.1:
     resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
     engines: {node: '>=8'}
@@ -2420,6 +2961,11 @@ packages:
       min-indent: 1.0.1
     dev: true
 
+  /strip-json-comments@2.0.1:
+    resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
+    engines: {node: '>=0.10.0'}
+    dev: false
+
   /strip-json-comments@3.1.1:
     resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
     engines: {node: '>=8'}
@@ -2570,6 +3116,26 @@ packages:
       periscopic: 3.1.0
     dev: true
 
+  /tar-fs@2.1.1:
+    resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==}
+    dependencies:
+      chownr: 1.1.4
+      mkdirp-classic: 0.5.3
+      pump: 3.0.0
+      tar-stream: 2.2.0
+    dev: false
+
+  /tar-stream@2.2.0:
+    resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
+    engines: {node: '>=6'}
+    dependencies:
+      bl: 4.1.0
+      end-of-stream: 1.4.4
+      fs-constants: 1.0.0
+      inherits: 2.0.4
+      readable-stream: 3.6.2
+    dev: false
+
   /text-table@0.2.0:
     resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
     dev: true
@@ -2618,7 +3184,12 @@ packages:
 
   /tslib@2.6.2:
     resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
-    dev: true
+
+  /tunnel-agent@0.6.0:
+    resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
+    dependencies:
+      safe-buffer: 5.2.1
+    dev: false
 
   /type-check@0.4.0:
     resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
@@ -2659,7 +3230,6 @@ packages:
 
   /util-deprecate@1.0.2:
     resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
-    dev: true
 
   /util@0.12.5:
     resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==}
@@ -2824,7 +3394,6 @@ packages:
 
   /wrappy@1.0.2:
     resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
-    dev: true
 
   /yallist@4.0.0:
     resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
diff --git a/src/auth.ts b/src/auth.ts
new file mode 100644
index 0000000..2a345be
--- /dev/null
+++ b/src/auth.ts
@@ -0,0 +1,10 @@
+import { AIDC_CLIENT_ID, AIDC_CLIENT_SECRET } from "$env/static/private";
+import { AriseIdConnect } from "$lib/index.js";
+import { defaultLucia } from "$lib/default_lucia.js";
+
+export const aidc = await AriseIdConnect.init({
+  client_id: AIDC_CLIENT_ID,
+  client_secret: AIDC_CLIENT_SECRET,
+  scope: "openid offline profile",
+  luciaWrapper: defaultLucia,
+});
diff --git a/src/hooks.server.ts b/src/hooks.server.ts
new file mode 100644
index 0000000..6104748
--- /dev/null
+++ b/src/hooks.server.ts
@@ -0,0 +1,3 @@
+import { aidc } from "./auth.js";
+
+export const handle = aidc.handler();
diff --git a/src/lib/cookies.ts b/src/lib/cookies.ts
deleted file mode 100644
index 5a30671..0000000
--- a/src/lib/cookies.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import { JWTCookieManager } from "$lib/utils/jwt_cookie.js";
-import type { Cookies } from "@sveltejs/kit";
-import type { TokenSet } from "openid-client";
-import { z } from "zod";
-import type { InternalConfig, Config } from "$lib/types.js";
-
-const ONE_HOUR = 60 * 60;
-const ONE_DAY = 24 * ONE_HOUR;
-const ONE_MONTH = 30 * ONE_DAY;
-
-export const authSchema = z.object({
-  codeVerifier: z.string(),
-  state: z.string(),
-  nonce: z.string(),
-});
-
-export type AuthCookie = JWTCookieManager<typeof authSchema>;
-
-export function authCookieBuilder({ jwt_secret }: Config): AuthCookie {
-  return new JWTCookieManager(authSchema, jwt_secret, {
-    serialize: { path: "/", maxAge: ONE_HOUR },
-    name: "tmp_arise_auth_secrets",
-  });
-}
-
-export const tokenSetSchema = z.object({
-  access_token: z.string().optional(),
-  token_type: z.string().optional(),
-  id_token: z.string().optional(),
-  refresh_token: z.string().optional(),
-  scope: z.string().optional(),
-  expires_at: z.number().optional(),
-  session_state: z.string().optional(),
-});
-
-export type TokenSetCookie = JWTCookieManager<typeof tokenSetSchema>;
-
-export function tokenSetCookieBuilder({ jwt_secret }: Config): TokenSetCookie {
-  return new JWTCookieManager(tokenSetSchema, jwt_secret, {
-    name: "arise_token_set",
-    serialize: {
-      maxAge: ONE_MONTH,
-    },
-  });
-}
-
-export async function setTokenSetCookie(
-  tokenSet: TokenSet,
-  cookies: Cookies,
-  config: InternalConfig,
-) {
-  const userinfo = await config.client.userinfo(tokenSet);
-
-  config.cookies.tokenSet.send(cookies, {
-    payload: tokenSet,
-    jwt: { subject: userinfo.sub, expiresIn: ONE_MONTH },
-  });
-}
diff --git a/src/lib/default_lucia.ts b/src/lib/default_lucia.ts
new file mode 100644
index 0000000..1ffaf41
--- /dev/null
+++ b/src/lib/default_lucia.ts
@@ -0,0 +1,65 @@
+import { BetterSqlite3Adapter } from "@lucia-auth/adapter-sqlite";
+import sqlite from "better-sqlite3";
+import type { Database as SqLiteConnection } from "better-sqlite3";
+import { LuciaWrapper } from "./lucia.js";
+
+class DefaultLucia extends LuciaWrapper<DatabaseUser> {
+  db: SqLiteConnection;
+
+  constructor() {
+    let db = sqlite(":memory:");
+    super(
+      new BetterSqlite3Adapter(db, {
+        user: "user",
+        session: "session",
+      }),
+    );
+    this.db = db;
+  }
+
+  initDatabase() {
+    this.db.exec(`CREATE TABLE IF NOT EXISTS user (
+      id TEXT NOT NULL PRIMARY KEY,
+      subject TEXT NOT NULL UNIQUE
+    )`);
+
+    this.db.exec(`CREATE TABLE IF NOT EXISTS session (
+      id TEXT NOT NULL PRIMARY KEY,
+      expires_at INTEGER NOT NULL,
+      user_id TEXT NOT NULL,
+      id_token TEXT NOT NULL,
+      FOREIGN KEY (user_id) REFERENCES user(id)
+    )`);
+  }
+
+  getUser(subject: string) {
+    return this.db
+      .prepare("SELECT * FROM user WHERE subject = ?")
+      .get(subject) as DatabaseUser | undefined;
+  }
+
+  createUser(subject: string, userId: string) {
+    this.db
+      .prepare("INSERT INTO user (id, subject) VALUES (?, ?)")
+      .run(userId, subject);
+  }
+}
+
+export const defaultLucia = new DefaultLucia();
+
+declare module "lucia" {
+  interface Register {
+    Lucia: typeof defaultLucia.lucia;
+    DatabaseUserAttributes: Omit<DatabaseUser, "id">;
+    DatabaseSessionAttributes: Omit<DatabaseSession, "id">;
+  }
+}
+
+export interface DatabaseUser {
+  id: string;
+  subject: string;
+}
+export interface DatabaseSession {
+  id: string;
+  id_token: string;
+}
diff --git a/src/lib/handlers/cookie.ts b/src/lib/handlers/cookie.ts
deleted file mode 100644
index c30e239..0000000
--- a/src/lib/handlers/cookie.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { TokenSet } from "openid-client";
-import { setTokenSetCookie } from "$lib/cookies.js";
-import type { Handle } from "@sveltejs/kit";
-import type { InternalConfig } from "$lib/types.js";
-
-export default function (config: InternalConfig): Handle {
-  return async function ({ event, resolve }) {
-    const jwtTokenSet = config.cookies.tokenSet.receive(event.cookies);
-
-    if (jwtTokenSet !== null) {
-      let tokenSet = new TokenSet(jwtTokenSet);
-
-      try {
-        if (tokenSet.expired()) {
-          tokenSet = await config.client.refresh(tokenSet);
-
-          await setTokenSetCookie(tokenSet, event.cookies, config);
-        }
-
-        event.locals.arise.loggedIn = {
-          user: { id: tokenSet.claims().sub },
-          tokenSet,
-        };
-      } catch (error) {
-        console.error(error);
-        config.cookies.tokenSet.delete(event.cookies);
-      }
-    }
-
-    return resolve(event);
-  };
-}
diff --git a/src/lib/handlers/index.ts b/src/lib/handlers/index.ts
deleted file mode 100644
index 2a93eca..0000000
--- a/src/lib/handlers/index.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { sequence } from "@sveltejs/kit/hooks";
-import cookieHandler from "./cookie.js";
-import loginHandler from "./login.js";
-import logoutHandler from "./logout.js";
-import loginCallbackHandler from "./loginCallback.js";
-import logoutCallbackHandler from "./logoutCallback.js";
-import type { Handle } from "@sveltejs/kit";
-import type { InternalConfig } from "$lib/types.js";
-
-const init: Handle = ({ event, resolve }) => {
-  event.locals.arise = {};
-  return resolve(event);
-};
-
-function route(pathname: string, handler: Handle): Handle {
-  return ({ event, resolve }) => {
-    if (event.url.pathname === pathname) {
-      return handler({ event, resolve });
-    }
-
-    return resolve(event);
-  };
-}
-
-export default function (config: InternalConfig): Handle {
-  const { paths } = config;
-  return sequence(
-    init,
-    cookieHandler(config),
-    route(paths.login, loginHandler(config)),
-    route(paths.logout, logoutHandler(config)),
-    route(paths.callback, loginCallbackHandler(config)),
-    route(paths.logoutCallback, logoutCallbackHandler(config)),
-  );
-}
diff --git a/src/lib/handlers/login.ts b/src/lib/handlers/login.ts
deleted file mode 100644
index f2e906a..0000000
--- a/src/lib/handlers/login.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { redirect } from "@sveltejs/kit";
-import { generators } from "openid-client";
-import type { Handle } from "@sveltejs/kit";
-import type { InternalConfig } from "$lib/types.js";
-import { SEE_OTHER } from "readable-http-codes";
-
-export default function (config: InternalConfig): Handle {
-  return async function ({ event }) {
-    const codeVerifier = generators.codeVerifier();
-    const state = generators.state();
-    const nonce = generators.nonce();
-
-    config.cookies.auth.send(event.cookies, {
-      payload: { codeVerifier, state, nonce },
-    });
-
-    const redirectURI = new URL(config.paths.callback, event.url).toString();
-    const authorizationUrl = config.client.authorizationUrl({
-      response_type: "code",
-      scope: config.scope,
-      code_challenge: generators.codeChallenge(codeVerifier),
-      code_challenge_method: "S256",
-      redirect_uri: redirectURI,
-      state,
-      nonce,
-    });
-
-    redirect(SEE_OTHER, authorizationUrl);
-  };
-}
diff --git a/src/lib/handlers/loginCallback.ts b/src/lib/handlers/loginCallback.ts
deleted file mode 100644
index a7db2a0..0000000
--- a/src/lib/handlers/loginCallback.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { type Handle, redirect } from "@sveltejs/kit";
-import { setTokenSetCookie } from "$lib/cookies.js";
-import { errors } from "openid-client";
-import type { InternalConfig } from "$lib/types.js";
-import { base } from "$app/paths";
-import { SEE_OTHER } from "readable-http-codes";
-
-export default function (config: InternalConfig): Handle {
-  return async function ({ event, resolve }) {
-    const params = config.client.callbackParams(event.url.toString());
-
-    const cookie = config.cookies.auth.receive(event.cookies);
-
-    if (cookie === null) {
-      redirect(SEE_OTHER, config.paths.login);
-    }
-
-    try {
-      const redirectURI = new URL(config.paths.callback, event.url).toString();
-      const tokenSet = await config.client.callback(redirectURI, params, {
-        code_verifier: cookie.codeVerifier,
-        state: cookie.state,
-        nonce: cookie.nonce,
-      });
-
-      await setTokenSetCookie(tokenSet, event.cookies, config);
-    } catch (error) {
-      if (error instanceof errors.OPError) {
-        if (error.error === "access_denied") {
-          if (config.on?.accessDenied) {
-            return config.on.accessDenied(event, error);
-          }
-
-          redirect(SEE_OTHER, `${base}/`);
-        }
-
-        event.locals.arise.error = error;
-        return resolve(event);
-      } else if (error instanceof errors.RPError) {
-        event.locals.arise.error = error;
-        return resolve(event);
-      }
-
-      throw error;
-    }
-
-    if (config.on?.loggedIn) {
-      return config.on.loggedIn(event);
-    }
-
-    redirect(SEE_OTHER, `${base}/`);
-  };
-}
diff --git a/src/lib/handlers/logout.ts b/src/lib/handlers/logout.ts
deleted file mode 100644
index 6a18280..0000000
--- a/src/lib/handlers/logout.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { redirect } from "@sveltejs/kit";
-import type { Handle } from "@sveltejs/kit";
-import type { InternalConfig } from "$lib/types.js";
-import { SEE_OTHER } from "readable-http-codes";
-
-export default function (config: InternalConfig): Handle {
-  return async function ({ event }) {
-    const postLogoutRedirectUTI = new URL(
-      config.paths.logoutCallback,
-      event.url,
-    ).toString();
-    const endSessionUrl = config.client.endSessionUrl({
-      post_logout_redirect_uri: postLogoutRedirectUTI,
-      id_token_hint: event.locals.arise.loggedIn?.tokenSet,
-    });
-
-    redirect(SEE_OTHER, endSessionUrl);
-  };
-}
diff --git a/src/lib/handlers/logoutCallback.ts b/src/lib/handlers/logoutCallback.ts
deleted file mode 100644
index f8b4e05..0000000
--- a/src/lib/handlers/logoutCallback.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { type Handle, redirect } from "@sveltejs/kit";
-import type { InternalConfig } from "$lib/types.js";
-import { SEE_OTHER } from "readable-http-codes";
-
-export default function (config: InternalConfig): Handle {
-  return async function ({ event }) {
-    config.cookies.tokenSet.delete(event.cookies);
-
-    if (config.on?.loggedOut) {
-      return config.on.loggedOut(event);
-    }
-
-    redirect(SEE_OTHER, "/");
-  };
-}
diff --git a/src/lib/helpers.ts b/src/lib/helpers.ts
new file mode 100644
index 0000000..868a3a2
--- /dev/null
+++ b/src/lib/helpers.ts
@@ -0,0 +1,48 @@
+import { base } from "$app/paths";
+import type { Handle, RequestEvent } from "@sveltejs/kit";
+import type { Cookie as LuciaCookie } from "lucia";
+
+export function setLuciaCookie(event: RequestEvent, cookie: LuciaCookie) {
+  event.cookies.set(cookie.name, cookie.value, {
+    path: ".",
+    ...cookie.attributes,
+  });
+}
+
+export function setTempCookie(
+  event: RequestEvent,
+  name: string,
+  value: string,
+) {
+  event.cookies.set(name, value, {
+    path: "/",
+    secure: import.meta.env.PROD,
+    httpOnly: true,
+    maxAge: 60 * 10,
+    sameSite: "lax",
+  });
+}
+
+export function route(pathname: string, handler: Handle): Handle {
+  return ({ event, resolve }) => {
+    if (event.url.pathname === pathname) {
+      return handler({ event, resolve });
+    }
+
+    return resolve(event);
+  };
+}
+
+export function method(method: string, handler: Handle): Handle {
+  return ({ event, resolve }) => {
+    if (event.request.method === method) {
+      return handler({ event, resolve });
+    }
+
+    return resolve(event);
+  };
+}
+
+export function addBasePath(path: string): string {
+  return `${base}${path}`;
+}
diff --git a/src/lib/index.ts b/src/lib/index.ts
index 8ea634a..e991be7 100644
--- a/src/lib/index.ts
+++ b/src/lib/index.ts
@@ -1,46 +1,216 @@
-import { Issuer } from "openid-client";
-import { base } from "$app/paths";
-import handler from "./handlers/index.js";
-import type { Config, InternalConfig } from "./types.js";
-import * as paths from "./paths.js";
-import { authCookieBuilder, tokenSetCookieBuilder } from "./cookies.js";
-
-export { handleBuilder as ariseIdConnectBuilder } from "./index.js";
-export * from "./public.js";
-
-function addBase(path: string): string {
-  return `${base}${path}`;
-}
+import { redirect, type Handle, error, type RequestEvent } from "@sveltejs/kit";
+import { sequence } from "@sveltejs/kit/hooks";
+import { Lucia, generateId } from "lucia";
+import type { Client, TokenSet } from "openid-client";
+import { Issuer, errors, generators } from "openid-client";
+import { SEE_OTHER } from "readable-http-codes";
+import {
+  method,
+  route,
+  setLuciaCookie,
+  setTempCookie,
+  addBasePath,
+} from "./helpers.js";
+import type { Config, CookieNames, Paths } from "./types.js";
+import type { DatabaseUser } from "./lucia.js";
+
+export class AriseIdConnect<DbUser extends DatabaseUser> {
+  readonly client: Client;
+  readonly paths: Paths;
+  protected cookieNames: CookieNames;
+
+  constructor(
+    readonly config: Config<DbUser>,
+    issuer: Issuer,
+  ) {
+    this.client = new issuer.Client({
+      ...config,
+      grant_types: ["authorization_code", "refresh_token"],
+      response_types: ["code", "id_token"],
+    });
+
+    this.cookieNames = {
+      oauthState: config.cookieNames?.oauthState ?? "aidc_state",
+      oauthCodeVerifier:
+        config.cookieNames?.oauthCodeVerifier ?? "aidc_code_verifier",
+    };
+
+    this.paths = {
+      home: addBasePath(config.paths?.home ?? "/"),
+      callback: addBasePath(config.paths?.callback ?? "/auth/callback"),
+      logoutCallback: addBasePath(config.paths?.logoutCallback ?? "/"),
+      login: addBasePath(config.paths?.login ?? "/auth/login"),
+      logout: addBasePath(config.paths?.logout ?? "/auth/logout"),
+    };
+  }
+
+  static async init<DbUser extends DatabaseUser>(
+    config: Config<DbUser>,
+  ): Promise<AriseIdConnect<DbUser>> {
+    const issuer = await Issuer.discover(
+      config.issuer ||
+        "https://oidc.iiens.net/.well-known/openid-configuration",
+    );
+    const aidc = new AriseIdConnect(config, issuer);
+
+    await aidc.config.luciaWrapper.initDatabase();
+    return aidc;
+  }
+
+  protected async getTokens(event: RequestEvent): Promise<TokenSet> {
+    const state = event.cookies.get(this.cookieNames.oauthState) ?? null;
+    const codeVerifier =
+      event.cookies.get(this.cookieNames.oauthCodeVerifier) ?? null;
+    const params = this.client.callbackParams(event.url.toString());
+
+    if (!codeVerifier || !state) {
+      redirect(SEE_OTHER, this.paths.login);
+    }
+
+    const redirectURI = new URL(this.paths.callback, event.url).toString();
+    try {
+      const tokenSet = await this.client.callback(redirectURI, params, {
+        code_verifier: codeVerifier,
+        state: state,
+      });
+
+      return tokenSet;
+    } catch (err) {
+      if (err instanceof errors.OPError) {
+        if (err.error === "access_denied") {
+          if (this.config.on?.accessDenied) {
+            return this.config.on.accessDenied(event, err);
+          }
+
+          redirect(SEE_OTHER, this.paths.home);
+        }
+
+        error(500, err);
+      } else if (err instanceof errors.RPError) {
+        error(500, err);
+      }
+
+      throw err;
+    }
+  }
+
+  protected loginHandler: Handle = ({ event }) => {
+    const codeVerifier = generators.codeVerifier();
+    const state = generators.state();
+    // const nonce = generators.nonce();
+
+    const redirectURI = new URL(this.paths.callback, event.url).toString();
+    const authorizationUrl = this.client.authorizationUrl({
+      response_type: "code",
+      scope: this.config.scope,
+      code_challenge: generators.codeChallenge(codeVerifier),
+      code_challenge_method: "S256",
+      redirect_uri: redirectURI,
+      state,
+      // nonce,
+    });
+
+    setTempCookie(event, this.cookieNames.oauthState, state);
+    setTempCookie(event, this.cookieNames.oauthCodeVerifier, codeVerifier);
+
+    redirect(SEE_OTHER, authorizationUrl);
+  };
+
+  protected logoutHandler: Handle = async ({ event }) => {
+    if (!event.locals.session) {
+      redirect(SEE_OTHER, this.paths.home);
+    }
+
+    const { lucia } = this.config.luciaWrapper;
+
+    const { session } = await lucia.validateSession(event.locals.session.id);
 
-export async function handleBuilder(config: Config) {
-  const issuer = await Issuer.discover(
-    config.issuer || "https://oidc.iiens.net/.well-known/openid-configuration",
-  );
-
-  const client = new issuer.Client({
-    ...config,
-    grant_types: ["authorization_code", "refresh_token"],
-    response_types: ["code", "id_token"],
-  });
-
-  const flowPaths: InternalConfig["paths"] = {
-    callback: addBase(config.paths?.callback ?? paths.CALLBACK),
-    logoutCallback: addBase(
-      config.paths?.logoutCallback ?? paths.LOGOUT_CALLBACK,
-    ),
-    login: addBase(config.paths?.login ?? paths.LOGIN),
-    logout: addBase(config.paths?.logout ?? paths.LOGOUT),
+    const postLogoutRedirectURI = new URL(this.paths.logoutCallback, event.url);
+    const endSessionUrl = this.client.endSessionUrl({
+      post_logout_redirect_uri: postLogoutRedirectURI.toString(),
+      id_token_hint: session?.id_token,
+    });
+
+    if (this.config.on?.logout) {
+      await this.config.on.logout(event);
+    }
+
+    await lucia.invalidateSession(event.locals.session.id);
+
+    setLuciaCookie(event, lucia.createBlankSessionCookie());
+
+    redirect(SEE_OTHER, endSessionUrl);
+  };
+
+  protected callbackHandler: Handle = async ({ event }) => {
+    const tokenSet = await this.getTokens(event);
+
+    if (!tokenSet.id_token) {
+      throw new Error("No id_token in tokenSet");
+    }
+
+    const subject = tokenSet.claims().sub;
+
+    const { luciaWrapper } = this.config;
+    const { lucia } = luciaWrapper;
+
+    const existingUser = await luciaWrapper.getUser(subject);
+
+    const userId = existingUser ? existingUser.id : generateId(15);
+
+    if (!existingUser) {
+      await luciaWrapper.createUser(subject, userId);
+    }
+
+    const session = await lucia.createSession(userId, {
+      id_token: tokenSet.id_token,
+    });
+    setLuciaCookie(event, lucia.createSessionCookie(session.id));
+
+    if (this.config.on?.login) {
+      return this.config.on.login(event, tokenSet.claims());
+    }
+
+    redirect(SEE_OTHER, this.paths.home);
   };
 
-  const cookies: InternalConfig["cookies"] = {
-    auth: authCookieBuilder(config),
-    tokenSet: tokenSetCookieBuilder(config),
+  protected sessionHandler: Handle = async ({ event, resolve }) => {
+    const { lucia } = this.config.luciaWrapper;
+
+    const sessionId = event.cookies.get(lucia.sessionCookieName);
+    if (!sessionId) {
+      event.locals.user = null;
+      event.locals.session = null;
+      return resolve(event);
+    }
+
+    const { session, user } = await lucia.validateSession(sessionId);
+
+    if (session && session.fresh) {
+      setLuciaCookie(event, lucia.createSessionCookie(session.id));
+    }
+    if (!session) {
+      setLuciaCookie(event, lucia.createBlankSessionCookie());
+    }
+
+    event.locals.user = user;
+    event.locals.session = session;
+    return resolve(event);
+  };
+
+  protected miscHandler: Handle = ({ event, resolve }) => {
+    event.locals.authPaths = this.paths;
+
+    return resolve(event);
   };
 
-  return handler({
-    ...config,
-    paths: flowPaths,
-    client,
-    cookies,
-  });
+  handler(): Handle {
+    return sequence(
+      this.miscHandler.bind(this),
+      this.sessionHandler.bind(this),
+      route(this.paths.login, this.loginHandler.bind(this)),
+      route(this.paths.logout, method("POST", this.logoutHandler.bind(this))),
+      route(this.paths.callback, this.callbackHandler.bind(this)),
+    );
+  }
 }
diff --git a/src/lib/lucia.ts b/src/lib/lucia.ts
new file mode 100644
index 0000000..a52426c
--- /dev/null
+++ b/src/lib/lucia.ts
@@ -0,0 +1,65 @@
+import { dev } from "$app/environment";
+import type { MaybePromise } from "@sveltejs/kit";
+import type {
+  Adapter,
+  RegisteredDatabaseSessionAttributes,
+  RegisteredDatabaseUserAttributes,
+  SessionCookieOptions,
+  TimeSpan,
+} from "lucia";
+import { Lucia } from "lucia";
+
+export interface DatabaseUser {
+  id: string;
+  subject: string;
+}
+export interface DatabaseSession {
+  id: string;
+  id_token: string;
+}
+
+export abstract class LuciaWrapper<
+  DbUser extends DatabaseUser,
+  _SessionAttributes extends { id_token: string } = { id_token: string },
+  _UserAttributes extends { subject: string } = { subject: string },
+> {
+  lucia: Lucia<_SessionAttributes, _UserAttributes>;
+
+  constructor(
+    adapter: Adapter,
+    options?: {
+      sessionExpiresIn?: TimeSpan;
+      sessionCookie?: SessionCookieOptions;
+      getSessionAttributes?: (
+        databaseSessionAttributes: RegisteredDatabaseSessionAttributes,
+      ) => _SessionAttributes;
+      getUserAttributes?: (
+        databaseUserAttributes: RegisteredDatabaseUserAttributes,
+      ) => _UserAttributes;
+    },
+  ) {
+    this.lucia = new Lucia(adapter, {
+      sessionCookie: {
+        attributes: {
+          secure: !dev,
+        },
+        name: "aidc_session",
+      },
+      getUserAttributes(attributes) {
+        return {
+          subject: attributes.subject,
+        };
+      },
+      getSessionAttributes(attributes) {
+        return {
+          id_token: attributes.id_token,
+        };
+      },
+      ...options,
+    });
+  }
+
+  abstract initDatabase(): MaybePromise<void>;
+  abstract getUser(subject: string): MaybePromise<DbUser | undefined>;
+  abstract createUser(subject: string, userId: string): MaybePromise<void>;
+}
diff --git a/src/lib/paths.ts b/src/lib/paths.ts
deleted file mode 100644
index 1b28a50..0000000
--- a/src/lib/paths.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export const BASE = "/auth";
-export const CALLBACK = `${BASE}/callback`;
-export const LOGOUT_CALLBACK = `${BASE}/callback/logout`;
-export const LOGIN = `${BASE}/login`;
-export const LOGOUT = `${BASE}/logout`;
diff --git a/src/lib/public.ts b/src/lib/public.ts
deleted file mode 100644
index b9f22af..0000000
--- a/src/lib/public.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export * as openid_client from "openid-client";
-export * as cookie from "cookie";
-export * as jsonwebtoken from "jsonwebtoken";
-export type { AuthData as AriseData, Config } from "./types.js";
-export * as authPaths from "./paths.js";
diff --git a/src/lib/types.ts b/src/lib/types.ts
index 0995ebf..73ffbb8 100644
--- a/src/lib/types.ts
+++ b/src/lib/types.ts
@@ -1,60 +1,36 @@
 import type { MaybePromise, RequestEvent } from "@sveltejs/kit";
-import type {
-  BaseClient,
-  ClientMetadata,
-  errors,
-  TokenSet,
-} from "openid-client";
-import type { AuthCookie, TokenSetCookie } from "./cookies.js";
+import type { ClientMetadata, UserinfoResponse, errors } from "openid-client";
+import type { DatabaseUser, LuciaWrapper } from "./lucia.js";
 
-export type AuthData = {
-  loggedIn?: {
-    user: {
-      id: string;
-    };
-    tokenSet: TokenSet;
+export interface Config<DbUser extends DatabaseUser> extends ClientMetadata {
+  client_secret: string;
+  scope: string;
+  issuer?: string;
+  on?: {
+    accessDenied?: (event: RequestEvent, error: errors.OPError) => never;
+    login?: (event: RequestEvent, userInfo: UserinfoResponse) => never;
+    logout?: (event: RequestEvent) => MaybePromise<void>;
   };
-  error?: errors.OPError | errors.RPError;
-};
-
-declare global {
-  // eslint-disable-next-line @typescript-eslint/no-namespace
-  namespace App {
-    interface Locals {
-      arise: AuthData;
-    }
-  }
+  luciaWrapper: LuciaWrapper<DbUser>;
+  cookieNames?: Partial<CookieNames>;
+  paths?: Partial<Paths>;
 }
 
-type Paths = {
+export type Paths = {
+  home: string;
   callback: string;
   logoutCallback: string;
   login: string;
   logout: string;
 };
 
-export interface Config extends ClientMetadata {
-  client_secret: string;
-  scope: string;
-  /** JWT Secret */
-  jwt_secret: string;
-  paths?: Partial<Paths>;
-  issuer?: string;
-  on?: {
-    accessDenied?: (
-      event: RequestEvent,
-      error: errors.OPError,
-    ) => MaybePromise<Response>;
-    loggedIn?: (event: RequestEvent) => MaybePromise<Response>;
-    loggedOut?: (event: RequestEvent) => MaybePromise<Response>;
-  };
-}
+export type CookieNames = {
+  oauthState: string;
+  oauthCodeVerifier: string;
+};
 
-export interface InternalConfig extends Config {
-  client: BaseClient;
-  paths: Paths;
-  cookies: {
-    auth: AuthCookie;
-    tokenSet: TokenSetCookie;
-  };
+export interface Locals {
+  user: import("lucia").User | null;
+  session: import("lucia").Session | null;
+  authPaths: Paths;
 }
diff --git a/src/lib/utils/jwt_cookie.ts b/src/lib/utils/jwt_cookie.ts
deleted file mode 100644
index 99dca0c..0000000
--- a/src/lib/utils/jwt_cookie.ts
+++ /dev/null
@@ -1,77 +0,0 @@
-import type { Cookies } from "@sveltejs/kit";
-import jwt from "jsonwebtoken";
-import type { z, ZodObject, ZodSchema } from "zod";
-import type { CookieSerializeOptions } from "cookie";
-import { dev } from "$app/environment";
-
-type Empty = Record<string, never>;
-
-export type JWT<T extends ZodSchema = ZodObject<Empty>> = jwt.JwtPayload &
-  z.output<T>;
-
-export interface SendOptions<T> {
-  payload: T;
-  jwt?: jwt.SignOptions;
-}
-
-export interface CookieOptions {
-  serialize?: CookieSerializeOptions;
-  name?: string;
-}
-
-export class JWTCookieManager<T extends ZodSchema = ZodObject<Empty>> {
-  constructor(
-    private schema: T,
-    private secret: string,
-    private cookieOptions?: CookieOptions,
-  ) {}
-
-  protected cookieName(): string {
-    return this.cookieOptions?.name ?? "flash";
-  }
-  protected cookieSerializeOptions(): CookieSerializeOptions & {
-    path: string;
-  } {
-    return {
-      path: "/",
-      httpOnly: true,
-      secure: !dev,
-      sameSite: !dev,
-      ...this.cookieOptions?.serialize,
-    };
-  }
-
-  send(cookies: Cookies, options: SendOptions<z.output<T>>) {
-    const payload = this.schema.safeParse(options.payload);
-
-    if (!payload.success) {
-      throw payload.error;
-    }
-
-    const cookie = jwt.sign(payload.data, this.secret, options.jwt);
-    cookies.set(this.cookieName(), cookie, this.cookieSerializeOptions());
-  }
-
-  receive(cookies: Cookies): JWT<T> | null {
-    const token = cookies.get(this.cookieName());
-
-    if (!token) return null;
-
-    try {
-      jwt.verify(token, this.secret);
-    } catch {
-      return null;
-    }
-
-    const payload = jwt.decode(token, { json: true });
-    if (payload) {
-      const { success } = this.schema.safeParse(payload);
-      return success ? (payload as JWT<T>) : null;
-    }
-    return null;
-  }
-
-  delete(cookies: Cookies) {
-    cookies.delete(this.cookieName(), this.cookieSerializeOptions());
-  }
-}
diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts
new file mode 100644
index 0000000..0e7d544
--- /dev/null
+++ b/src/routes/+layout.server.ts
@@ -0,0 +1,5 @@
+export function load({ locals }) {
+  return {
+    authPaths: locals.authPaths,
+  };
+}
diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts
new file mode 100644
index 0000000..751ee18
--- /dev/null
+++ b/src/routes/+page.server.ts
@@ -0,0 +1,10 @@
+import { redirect } from "@sveltejs/kit";
+
+export async function load(event) {
+  if (!event.locals.user) {
+    return redirect(302, "/login");
+  }
+  return {
+    user: event.locals.user,
+  };
+}
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
new file mode 100644
index 0000000..95f9e13
--- /dev/null
+++ b/src/routes/+page.svelte
@@ -0,0 +1,9 @@
+<script lang="ts">
+  export let data;
+</script>
+
+<h1>Hi, {data.user.subject}!</h1>
+<p>Your user ID is {data.user.id}.</p>
+<form method="post" action={data.authPaths.logout}>
+  <button>Sign out</button>
+</form>
diff --git a/src/routes/login/+page.server.ts b/src/routes/login/+page.server.ts
new file mode 100644
index 0000000..c465c92
--- /dev/null
+++ b/src/routes/login/+page.server.ts
@@ -0,0 +1,8 @@
+import { redirect } from "@sveltejs/kit";
+
+export async function load(event) {
+  if (event.locals.user) {
+    return redirect(302, "/");
+  }
+  return {};
+}
diff --git a/src/routes/login/+page.svelte b/src/routes/login/+page.svelte
new file mode 100644
index 0000000..ecf448c
--- /dev/null
+++ b/src/routes/login/+page.svelte
@@ -0,0 +1,6 @@
+<script lang="ts">
+  export let data;
+</script>
+
+<h1>Sign in</h1>
+<a href={data.authPaths.login}>Sign in with AriseID Connect</a>
diff --git a/static/favicon.png b/static/favicon.png
new file mode 100644
index 0000000000000000000000000000000000000000..825b9e65af7c104cfb07089bb28659393b4f2097
GIT binary patch
literal 1571
zcmV+;2Hg3HP)<h;3K|Lk000e1NJLTq004jh004jp1ONa4X*a1r00001b5ch_0Itp)
z=>Px)-AP12RCwC$UE6KzI1p6{F2N<Fgp}YCTtZ542`(WexCEDw|A=A)1A-t30wEX>
z1VK2vi|pOpn{~#djwYcWXTI_im_u^TJgMZ4JMOsSj!0ma>B?-(Hr@X&W@|R-$}W@Z
zg<YDbY-gN-C@$NXr>j#$x=!~7LGqHW?IO8+*oE1MyDp!G=L<gz=E*n%PA*mnuaD-%
zU>0#^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}^yfDSt7<V*0Nvi!iKPY@n`1~$se=LwHTOE7`*@+};uU_gT$1{Nib
z9&~5~nc4J(EWez%_f0Ty7T;wB{7s*oNO=9p8(o}N*_V>86oMTc!W&V;d?76)9KXX1
z+6Okem(d}YXmmOiZq$!IPk5t8nnS{%?+vDFz3BevmFN<oPlV?qzG@=jl`t@|E8zV8
zJ4}It4e%+dFPWw16u{sAX42jlFu{_e!AKZE75iV2&Q7>gpIod~R{>@#@5x9zJK<vg
zI0+@80NMa{gtNoRhsR-w%piFI3d-5xrN)R}H!WtK=Gp%dC5(a`Q0V4_vS-mj<((Z~
zbV*PucDY#zFVglY>EHLHv!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+an4TsL<j(YStU%^TNkjEqg808>y-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+<zzg4N+!Mudx;=&$zSp?cjRmyF-+404b-P66DA17$<$H}=
z7$P4)QXt>)#h1!*-HcGW@+gZ4B};0OnC~`xQOyH19z*TA!!<qA9l$G~6DRxiCy7Vo
zOl^oK!S3XbayWz&p33NSw$lxR1fm)O*rCE0^ZNmk0wnax!!?%g599+O37!666~F(y
z5fq?5T*Fm{^%YR^PiM%z#%x`fAC+;C&`X5JXN_40SieIEXoaUU=*}=c0OC7@a*rFE
z3xr3yyFB~zRl(lNY&B@$Fia%81M!xeIuTYNz!Dz6eBKONjL<@dJdT$H?ShKl6$p=F
ze!fdgzelJI&n`Hk9f}>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`<ao$!3dCkTn3@aF0ny6VrToO6qA
z-~&2=w&2nT&o5u>**$idgy-iMob4dJWWw|21b4NB=LfsYp0Aeh{Ov)yztQi;eL4y5
zMi>8^SzKqk8~k?UiQK^^-5d8c%bV?$F8%X~czyiaKCI2=UH<v&YL5FIcieFm_zQ2O
VHuiSbmk|H}002ovPDHLkV1f%e^4kCa

literal 0
HcmV?d00001

-- 
GitLab