From 815de9906a014c2eb1a4fe2bd8cf1b3077f03c9c Mon Sep 17 00:00:00 2001 From: Mathias Magnusson Date: Thu, 14 Aug 2025 18:42:27 +0200 Subject: Add passkey authentication --- .gitignore | 2 + migrations/0000_bitter_xorn.sql | 25 ++++++ migrations/0000_parallel_overlord.sql | 3 - migrations/meta/0000_snapshot.json | 125 ++++++++++++++++++++++++++- migrations/meta/_journal.json | 4 +- package.json | 7 +- pnpm-lock.yaml | 158 ++++++++++++++++++++++++++++++++++ src/auth.tsx | 93 ++++++++++++++++++++ src/db/schema.ts | 34 +++++++- src/index.tsx | 21 +++-- 10 files changed, 456 insertions(+), 16 deletions(-) create mode 100644 migrations/0000_bitter_xorn.sql delete mode 100644 migrations/0000_parallel_overlord.sql create mode 100644 src/auth.tsx diff --git a/.gitignore b/.gitignore index 36fabb6..719d2d9 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,5 @@ lerna-debug.log* # misc .DS_Store + +/data.db diff --git a/migrations/0000_bitter_xorn.sql b/migrations/0000_bitter_xorn.sql new file mode 100644 index 0000000..4f4c445 --- /dev/null +++ b/migrations/0000_bitter_xorn.sql @@ -0,0 +1,25 @@ +CREATE TABLE `groups` ( + `id` integer PRIMARY KEY NOT NULL, + `name` text NOT NULL +); +--> statement-breakpoint +CREATE TABLE `sessions` ( + `id` integer PRIMARY KEY NOT NULL, + `uuid` text NOT NULL, + `user_id` integer NOT NULL +); +--> statement-breakpoint +CREATE UNIQUE INDEX `sessions_uuid_unique` ON `sessions` (`uuid`);--> statement-breakpoint +CREATE TABLE `users` ( + `id` integer PRIMARY KEY NOT NULL, + `name` text NOT NULL, + `passkey` text, + `passkey_id` text NOT NULL +); +--> statement-breakpoint +CREATE UNIQUE INDEX `users_name_unique` ON `users` (`name`);--> statement-breakpoint +CREATE TABLE `webauthn_challenges` ( + `id` integer PRIMARY KEY NOT NULL, + `challenge` text NOT NULL, + `key` text NOT NULL +); diff --git a/migrations/0000_parallel_overlord.sql b/migrations/0000_parallel_overlord.sql deleted file mode 100644 index c1c0d28..0000000 --- a/migrations/0000_parallel_overlord.sql +++ /dev/null @@ -1,3 +0,0 @@ -CREATE TABLE `groups` ( - `name` text NOT NULL -); diff --git a/migrations/meta/0000_snapshot.json b/migrations/meta/0000_snapshot.json index 245d657..ba0da3c 100644 --- a/migrations/meta/0000_snapshot.json +++ b/migrations/meta/0000_snapshot.json @@ -1,18 +1,141 @@ { "version": "6", "dialect": "sqlite", - "id": "2b9ab579-4d91-4045-b35e-b5cceebe74de", + "id": "a2343f4c-71c5-4992-b49e-d30f2a4f08c0", "prevId": "00000000-0000-0000-0000-000000000000", "tables": { "groups": { "name": "groups", "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sessions": { + "name": "sessions", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "uuid": { + "name": "uuid", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "sessions_uuid_unique": { + "name": "sessions_uuid_unique", + "columns": [ + "uuid" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, "name": { "name": "name", "type": "text", "primaryKey": false, "notNull": true, "autoincrement": false + }, + "passkey": { + "name": "passkey", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "passkey_id": { + "name": "passkey_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "users_name_unique": { + "name": "users_name_unique", + "columns": [ + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "webauthn_challenges": { + "name": "webauthn_challenges", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "challenge": { + "name": "challenge", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false } }, "indexes": {}, diff --git a/migrations/meta/_journal.json b/migrations/meta/_journal.json index ccb08a8..3f28ad0 100644 --- a/migrations/meta/_journal.json +++ b/migrations/meta/_journal.json @@ -5,8 +5,8 @@ { "idx": 0, "version": "6", - "when": 1755176694946, - "tag": "0000_parallel_overlord", + "when": 1755189001309, + "tag": "0000_bitter_xorn", "breakpoints": true } ] diff --git a/package.json b/package.json index 831c042..fefacd0 100644 --- a/package.json +++ b/package.json @@ -11,11 +11,16 @@ }, "dependencies": { "@hono/node-server": "^1.18.2", + "@hono/zod-validator": "^0.7.2", "@libsql/client": "^0.15.11", + "@simplewebauthn/server": "^13.1.2", "drizzle-orm": "^0.44.4", - "hono": "^4.9.1" + "hono": "^4.9.1", + "superjson": "^2.2.2", + "zod": "^4.0.17" }, "devDependencies": { + "@simplewebauthn/types": "^12.0.0", "@types/node": "^20.11.17", "drizzle-kit": "^0.31.4", "tsx": "^4.7.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 87a2533..c7dcbbb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,16 +11,31 @@ importers: '@hono/node-server': specifier: ^1.18.2 version: 1.18.2(hono@4.9.1) + '@hono/zod-validator': + specifier: ^0.7.2 + version: 0.7.2(hono@4.9.1)(zod@4.0.17) '@libsql/client': specifier: ^0.15.11 version: 0.15.11 + '@simplewebauthn/server': + specifier: ^13.1.2 + version: 13.1.2 drizzle-orm: specifier: ^0.44.4 version: 0.44.4(@libsql/client@0.15.11) hono: specifier: ^4.9.1 version: 4.9.1 + superjson: + specifier: ^2.2.2 + version: 2.2.2 + zod: + specifier: ^4.0.17 + version: 4.0.17 devDependencies: + '@simplewebauthn/types': + specifier: ^12.0.0 + version: 12.0.0 '@types/node': specifier: ^20.11.17 version: 20.19.10 @@ -338,12 +353,24 @@ packages: cpu: [x64] os: [win32] + '@hexagon/base64@1.1.28': + resolution: {integrity: sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==} + '@hono/node-server@1.18.2': resolution: {integrity: sha512-icgNvC0vRYivzyuSSaUv9ttcwtN8fDyd1k3AOIBDJgYd84tXRZSS6na8X54CY/oYoFTNhEmZraW/Rb9XYwX4KA==} engines: {node: '>=18.14.1'} peerDependencies: hono: ^4 + '@hono/zod-validator@0.7.2': + resolution: {integrity: sha512-ub5eL/NeZ4eLZawu78JpW/J+dugDAYhwqUIdp9KYScI6PZECij4Hx4UsrthlEUutqDDhPwRI0MscUfNkvn/mqQ==} + peerDependencies: + hono: '>=3.9.0' + zod: ^3.25.0 || ^4.0.0 + + '@levischuck/tiny-cbor@0.2.11': + resolution: {integrity: sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow==} + '@libsql/client@0.15.11': resolution: {integrity: sha512-JB8RWRs+cAbHX35/dQ9wD3m4W5EVGevq1fFqiHKTT4Pa5HR7WrcGRVT+8NL2M7gtTlOvyPh9zzms2DPLBCswig==} @@ -408,15 +435,45 @@ packages: '@neon-rs/load@0.0.4': resolution: {integrity: sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==} + '@peculiar/asn1-android@2.4.0': + resolution: {integrity: sha512-YFueREq97CLslZZBI8dKzis7jMfEHSLxM+nr0Zdx1POiXFLjqqwoY5s0F1UimdBiEw/iKlHey2m56MRDv7Jtyg==} + + '@peculiar/asn1-ecc@2.4.0': + resolution: {integrity: sha512-fJiYUBCJBDkjh347zZe5H81BdJ0+OGIg0X9z06v8xXUoql3MFeENUX0JsjCaVaU9A0L85PefLPGYkIoGpTnXLQ==} + + '@peculiar/asn1-rsa@2.4.0': + resolution: {integrity: sha512-6PP75voaEnOSlWR9sD25iCQyLgFZHXbmxvUfnnDcfL6Zh5h2iHW38+bve4LfH7a60x7fkhZZNmiYqAlAff9Img==} + + '@peculiar/asn1-schema@2.4.0': + resolution: {integrity: sha512-umbembjIWOrPSOzEGG5vxFLkeM8kzIhLkgigtsOrfLKnuzxWxejAcUX+q/SoZCdemlODOcr5WiYa7+dIEzBXZQ==} + + '@peculiar/asn1-x509@2.4.0': + resolution: {integrity: sha512-F7mIZY2Eao2TaoVqigGMLv+NDdpwuBKU1fucHPONfzaBS4JXXCNCmfO0Z3dsy7JzKGqtDcYC1mr9JjaZQZNiuw==} + + '@simplewebauthn/server@13.1.2': + resolution: {integrity: sha512-VwoDfvLXSCaRiD+xCIuyslU0HLxVggeE5BL06+GbsP2l1fGf5op8e0c3ZtKoi+vSg1q4ikjtAghC23ze2Q3H9g==} + engines: {node: '>=20.0.0'} + + '@simplewebauthn/types@12.0.0': + resolution: {integrity: sha512-q6y8MkoV8V8jB4zzp18Uyj2I7oFp2/ONL8c3j8uT06AOWu3cIChc1au71QYHrP2b+xDapkGTiv+9lX7xkTlAsA==} + '@types/node@20.19.10': resolution: {integrity: sha512-iAFpG6DokED3roLSP0K+ybeDdIX6Bc0Vd3mLW5uDqThPWtNos3E+EqOM11mPQHKzfWHqEBuLjIlsBQQ8CsISmQ==} '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + asn1js@3.0.6: + resolution: {integrity: sha512-UOCGPYbl0tv8+006qks/dTgV9ajs97X2p0FAbyS2iyCRrmLSRolDaHdp+v/CLgnzHc3fVB+CwYiUmei7ndFcgA==} + engines: {node: '>=12.0.0'} + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + copy-anything@3.0.5: + resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} + engines: {node: '>=12.13'} + data-uri-to-buffer@4.0.1: resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} engines: {node: '>= 12'} @@ -565,6 +622,10 @@ packages: resolution: {integrity: sha512-qfvdJ42t6CQE0N/iSCa8KsW8SQqYD67YB+TYbwPHlnALvX+s7ynh8otR1NEk5jXtUg73gpV/B82OSufDmwtX3w==} engines: {node: '>=16.9.0'} + is-what@4.1.16: + resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} + engines: {node: '>=12.13'} + js-base64@3.7.8: resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} @@ -588,6 +649,13 @@ packages: promise-limit@2.7.0: resolution: {integrity: sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==} + pvtsutils@1.3.6: + resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==} + + pvutils@1.1.3: + resolution: {integrity: sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==} + engines: {node: '>=6.0.0'} + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} @@ -598,6 +666,13 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + superjson@2.2.2: + resolution: {integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==} + engines: {node: '>=16'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsx@4.20.4: resolution: {integrity: sha512-yyxBKfORQ7LuRt/BQKBXrpcq59ZvSW0XxwfjAt3w2/8PmdxaFzijtMhTawprSHhpzeM5BgU2hXHG3lklIERZXg==} engines: {node: '>=18.0.0'} @@ -635,6 +710,9 @@ packages: utf-8-validate: optional: true + zod@4.0.17: + resolution: {integrity: sha512-1PHjlYRevNxxdy2JZ8JcNAw7rX8V9P1AKkP+x/xZfxB0K5FYfuV+Ug6P/6NVSR2jHQ+FzDDoDHS04nYUsOIyLQ==} + snapshots: '@drizzle-team/brocli@0.10.2': {} @@ -793,10 +871,19 @@ snapshots: '@esbuild/win32-x64@0.25.9': optional: true + '@hexagon/base64@1.1.28': {} + '@hono/node-server@1.18.2(hono@4.9.1)': dependencies: hono: 4.9.1 + '@hono/zod-validator@0.7.2(hono@4.9.1)(zod@4.0.17)': + dependencies: + hono: 4.9.1 + zod: 4.0.17 + + '@levischuck/tiny-cbor@0.2.11': {} + '@libsql/client@0.15.11': dependencies: '@libsql/core': 0.15.11 @@ -861,6 +948,51 @@ snapshots: '@neon-rs/load@0.0.4': {} + '@peculiar/asn1-android@2.4.0': + dependencies: + '@peculiar/asn1-schema': 2.4.0 + asn1js: 3.0.6 + tslib: 2.8.1 + + '@peculiar/asn1-ecc@2.4.0': + dependencies: + '@peculiar/asn1-schema': 2.4.0 + '@peculiar/asn1-x509': 2.4.0 + asn1js: 3.0.6 + tslib: 2.8.1 + + '@peculiar/asn1-rsa@2.4.0': + dependencies: + '@peculiar/asn1-schema': 2.4.0 + '@peculiar/asn1-x509': 2.4.0 + asn1js: 3.0.6 + tslib: 2.8.1 + + '@peculiar/asn1-schema@2.4.0': + dependencies: + asn1js: 3.0.6 + pvtsutils: 1.3.6 + tslib: 2.8.1 + + '@peculiar/asn1-x509@2.4.0': + dependencies: + '@peculiar/asn1-schema': 2.4.0 + asn1js: 3.0.6 + pvtsutils: 1.3.6 + tslib: 2.8.1 + + '@simplewebauthn/server@13.1.2': + dependencies: + '@hexagon/base64': 1.1.28 + '@levischuck/tiny-cbor': 0.2.11 + '@peculiar/asn1-android': 2.4.0 + '@peculiar/asn1-ecc': 2.4.0 + '@peculiar/asn1-rsa': 2.4.0 + '@peculiar/asn1-schema': 2.4.0 + '@peculiar/asn1-x509': 2.4.0 + + '@simplewebauthn/types@12.0.0': {} + '@types/node@20.19.10': dependencies: undici-types: 6.21.0 @@ -869,8 +1001,18 @@ snapshots: dependencies: '@types/node': 20.19.10 + asn1js@3.0.6: + dependencies: + pvtsutils: 1.3.6 + pvutils: 1.1.3 + tslib: 2.8.1 + buffer-from@1.1.2: {} + copy-anything@3.0.5: + dependencies: + is-what: 4.1.16 + data-uri-to-buffer@4.0.1: {} debug@4.4.1: @@ -971,6 +1113,8 @@ snapshots: hono@4.9.1: {} + is-what@4.1.16: {} + js-base64@3.7.8: {} libsql@0.5.17: @@ -1000,6 +1144,12 @@ snapshots: promise-limit@2.7.0: {} + pvtsutils@1.3.6: + dependencies: + tslib: 2.8.1 + + pvutils@1.1.3: {} + resolve-pkg-maps@1.0.0: {} source-map-support@0.5.21: @@ -1009,6 +1159,12 @@ snapshots: source-map@0.6.1: {} + superjson@2.2.2: + dependencies: + copy-anything: 3.0.5 + + tslib@2.8.1: {} + tsx@4.20.4: dependencies: esbuild: 0.25.9 @@ -1029,3 +1185,5 @@ snapshots: web-streams-polyfill@3.3.3: {} ws@8.18.3: {} + + zod@4.0.17: {} diff --git a/src/auth.tsx b/src/auth.tsx new file mode 100644 index 0000000..ebda74e --- /dev/null +++ b/src/auth.tsx @@ -0,0 +1,93 @@ +import { Hono } from "hono"; +import * as swa from "@simplewebauthn/server"; +import { randomUUID } from "node:crypto"; +import { eq } from "drizzle-orm"; +import { RP_ID, ORIGIN, db } from "./index.js"; +import { sessionTable, userTable, webauthnChallenges } from "./db/schema.js"; +import { stringify, parse } from "superjson"; +import { setCookie } from "hono/cookie"; + +let app = new Hono(); + +export const LoginForm = () =>
+ + +
; + +app.post("/register-begin", async c => { + const username = randomUUID(); + let options = await swa.generateRegistrationOptions({ + rpName: "uneven", + rpID: RP_ID, + userName: username, + authenticatorSelection: { + residentKey: "required", + userVerification: "preferred", + }, + }); + await db.insert(webauthnChallenges).values({ key: "register:" + username, challenge: options.challenge }); + return c.html( +
+ ); +}); + +app.post("/register-finish", async c => { + let { resp, username } = await c.req.json(); + let [{ chall }] = await db.delete(webauthnChallenges).where(eq(webauthnChallenges.key, "register:" + username)).returning({ chall: webauthnChallenges.challenge }); + let r = await swa.verifyRegistrationResponse({ response: resp, expectedChallenge: chall, expectedOrigin: ORIGIN }); + if (!r.verified || !("registrationInfo" in r)) return c.html(

Could not verify registration response!

); + await db.insert(userTable).values({ name: username, passkey: stringify(r.registrationInfo), passkeyId: r.registrationInfo!.credential.id }); + return c.html( +

You now have an account!

+ ); +}); + +app.post("/login-begin", async c => { + let options = await swa.generateAuthenticationOptions({ + rpID: RP_ID, + userVerification: "preferred", + }); + let key = randomUUID(); + await db.insert(webauthnChallenges).values({ challenge: options.challenge, key: "login:" + key }); + return c.html( +
+ ); +}); + +app.post("/login-finish", async c => { + let { resp, key } = await c.req.json(); + let [{ chall }] = await db.delete(webauthnChallenges).where(eq(webauthnChallenges.key, "login:" + key)).returning({ chall: webauthnChallenges.challenge }); + let [{ id: userId, passkey }] = await db.select().from(userTable).where(user => eq(user.passkeyId, resp.id)); + if (!passkey) return c.html(

Who are you?

); + let r = await swa.verifyAuthenticationResponse({ + response: resp, + expectedChallenge: chall, + expectedOrigin: ORIGIN, + expectedRPID: RP_ID, + requireUserVerification: false, + credential: parse(passkey)!.credential, + }); + if (!r.verified) return c.html(

Could not verify authentication response!

); + let uuid = randomUUID(); + await db.insert(sessionTable).values({ userId, uuid }); + setCookie(c, "session", uuid); + return c.html(

Logged in!

); +}); + +export default app; diff --git a/src/db/schema.ts b/src/db/schema.ts index 15da652..01b7228 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -1,5 +1,33 @@ -import { text, sqliteTable } from "drizzle-orm/sqlite-core"; +import { relations } from "drizzle-orm"; +import { text, sqliteTable, integer } from "drizzle-orm/sqlite-core"; -export const groupsTable = sqliteTable("groups", { - name: text("name").notNull(), +export const groupTable = sqliteTable("groups", { + id: integer().primaryKey(), + name: text().notNull(), +}); + +export const userTable = sqliteTable("users", { + id: integer().primaryKey(), + name: text().unique().notNull(), + passkey: text(), + passkeyId: text("passkey_id").notNull(), +}); + +export const sessionTable = sqliteTable("sessions", { + id: integer().primaryKey(), + uuid: text().unique().notNull(), + userId: integer("user_id").notNull(), +}); + +export const sessionRelations = relations(sessionTable, ({ one }) => ({ + user: one(userTable, { + fields: [sessionTable.userId], + references: [userTable.id], + }), +})); + +export const webauthnChallenges = sqliteTable("webauthn_challenges", { + id: integer().primaryKey(), + challenge: text().notNull(), + key: text().notNull(), }); diff --git a/src/index.tsx b/src/index.tsx index c72d766..e029577 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2,13 +2,17 @@ import { serve } from "@hono/node-server"; import { Hono } from "hono"; import { createClient } from "@libsql/client"; import { drizzle } from "drizzle-orm/libsql"; -import { groupsTable } from "./db/schema.js"; +import { groupTable } from "./db/schema.js"; +import authRouter, { LoginForm } from "./auth.js"; -const app = new Hono(); -const db = drizzle(createClient({ url: "file:data.db" })); +export const RP_ID = "localhost"; // "uneven.0m.nu"; +export const ORIGIN = `http://${RP_ID}`; + +let app = new Hono(); +export let db = drizzle(createClient({ url: "file:data.db" })); async function Groups() { - const result = await db.select().from(groupsTable).all(); + let result = await db.select().from(groupTable).all(); return
    { result.map(group =>
  • {group.name}
  • ) @@ -20,10 +24,13 @@ app.get("/", c => c.html( - + + + Uneven + @@ -39,7 +46,9 @@ app.get("/button", c => c.html( >disco button! )); +app.route("/auth", authRouter); + serve({ fetch: app.fetch, - port: 3000, + port: 80, }, info => console.log(`Server is running on http://localhost:${info.port}`)); -- cgit v1.2.3