From 3262d631fd1be55a5c85ede08d92a35c5fb7d2c4 Mon Sep 17 00:00:00 2001 From: Mathias Magnusson Date: Mon, 18 Aug 2025 21:43:11 +0200 Subject: Get current user session --- migrations/0000_bitter_xorn.sql | 25 ------ migrations/0000_real_hitman.sql | 26 ++++++ migrations/0001_funny_ronan.sql | 13 +++ migrations/0002_red_morlun.sql | 13 +++ migrations/meta/0000_snapshot.json | 9 +- migrations/meta/0001_snapshot.json | 166 +++++++++++++++++++++++++++++++++++++ migrations/meta/0002_snapshot.json | 166 +++++++++++++++++++++++++++++++++++++ migrations/meta/_journal.json | 18 +++- src/auth.tsx | 26 ++++-- src/db/schema.ts | 3 +- src/index.tsx | 19 +++-- 11 files changed, 441 insertions(+), 43 deletions(-) delete mode 100644 migrations/0000_bitter_xorn.sql create mode 100644 migrations/0000_real_hitman.sql create mode 100644 migrations/0001_funny_ronan.sql create mode 100644 migrations/0002_red_morlun.sql create mode 100644 migrations/meta/0001_snapshot.json create mode 100644 migrations/meta/0002_snapshot.json diff --git a/migrations/0000_bitter_xorn.sql b/migrations/0000_bitter_xorn.sql deleted file mode 100644 index 4f4c445..0000000 --- a/migrations/0000_bitter_xorn.sql +++ /dev/null @@ -1,25 +0,0 @@ -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_real_hitman.sql b/migrations/0000_real_hitman.sql new file mode 100644 index 0000000..fc43e8e --- /dev/null +++ b/migrations/0000_real_hitman.sql @@ -0,0 +1,26 @@ +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, + `last_use` 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/0001_funny_ronan.sql b/migrations/0001_funny_ronan.sql new file mode 100644 index 0000000..9928b60 --- /dev/null +++ b/migrations/0001_funny_ronan.sql @@ -0,0 +1,13 @@ +PRAGMA foreign_keys=OFF;--> statement-breakpoint +CREATE TABLE `__new_sessions` ( + `id` integer PRIMARY KEY NOT NULL, + `uuid` text NOT NULL, + `user_id` integer NOT NULL, + `last_use` integer DEFAULT current_timestamp NOT NULL +); +--> statement-breakpoint +INSERT INTO `__new_sessions`("id", "uuid", "user_id", "last_use") SELECT "id", "uuid", "user_id", "last_use" FROM `sessions`;--> statement-breakpoint +DROP TABLE `sessions`;--> statement-breakpoint +ALTER TABLE `__new_sessions` RENAME TO `sessions`;--> statement-breakpoint +PRAGMA foreign_keys=ON;--> statement-breakpoint +CREATE UNIQUE INDEX `sessions_uuid_unique` ON `sessions` (`uuid`); \ No newline at end of file diff --git a/migrations/0002_red_morlun.sql b/migrations/0002_red_morlun.sql new file mode 100644 index 0000000..23de4ce --- /dev/null +++ b/migrations/0002_red_morlun.sql @@ -0,0 +1,13 @@ +PRAGMA foreign_keys=OFF;--> statement-breakpoint +CREATE TABLE `__new_sessions` ( + `id` integer PRIMARY KEY NOT NULL, + `uuid` text NOT NULL, + `user_id` integer NOT NULL, + `last_use` integer DEFAULT (unixepoch()) NOT NULL +); +--> statement-breakpoint +INSERT INTO `__new_sessions`("id", "uuid", "user_id", "last_use") SELECT "id", "uuid", "user_id", "last_use" FROM `sessions`;--> statement-breakpoint +DROP TABLE `sessions`;--> statement-breakpoint +ALTER TABLE `__new_sessions` RENAME TO `sessions`;--> statement-breakpoint +PRAGMA foreign_keys=ON;--> statement-breakpoint +CREATE UNIQUE INDEX `sessions_uuid_unique` ON `sessions` (`uuid`); \ No newline at end of file diff --git a/migrations/meta/0000_snapshot.json b/migrations/meta/0000_snapshot.json index ba0da3c..ec8a4df 100644 --- a/migrations/meta/0000_snapshot.json +++ b/migrations/meta/0000_snapshot.json @@ -1,7 +1,7 @@ { "version": "6", "dialect": "sqlite", - "id": "a2343f4c-71c5-4992-b49e-d30f2a4f08c0", + "id": "95d1eecd-0df6-4d23-8d18-cd455608bae7", "prevId": "00000000-0000-0000-0000-000000000000", "tables": { "groups": { @@ -51,6 +51,13 @@ "primaryKey": false, "notNull": true, "autoincrement": false + }, + "last_use": { + "name": "last_use", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false } }, "indexes": { diff --git a/migrations/meta/0001_snapshot.json b/migrations/meta/0001_snapshot.json new file mode 100644 index 0000000..e8a216d --- /dev/null +++ b/migrations/meta/0001_snapshot.json @@ -0,0 +1,166 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "b9c41e3d-8811-4705-8345-a3a8386748b8", + "prevId": "95d1eecd-0df6-4d23-8d18-cd455608bae7", + "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 + }, + "last_use": { + "name": "last_use", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "current_timestamp" + } + }, + "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": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/migrations/meta/0002_snapshot.json b/migrations/meta/0002_snapshot.json new file mode 100644 index 0000000..0d326c1 --- /dev/null +++ b/migrations/meta/0002_snapshot.json @@ -0,0 +1,166 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "29e91293-4ebe-4039-a781-8609bf9505d5", + "prevId": "b9c41e3d-8811-4705-8345-a3a8386748b8", + "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 + }, + "last_use": { + "name": "last_use", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "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": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/migrations/meta/_journal.json b/migrations/meta/_journal.json index 3f28ad0..a434ab9 100644 --- a/migrations/meta/_journal.json +++ b/migrations/meta/_journal.json @@ -5,8 +5,22 @@ { "idx": 0, "version": "6", - "when": 1755189001309, - "tag": "0000_bitter_xorn", + "when": 1755543105259, + "tag": "0000_real_hitman", + "breakpoints": true + }, + { + "idx": 1, + "version": "6", + "when": 1755543598172, + "tag": "0001_funny_ronan", + "breakpoints": true + }, + { + "idx": 2, + "version": "6", + "when": 1755543983317, + "tag": "0002_red_morlun", "breakpoints": true } ] diff --git a/src/auth.tsx b/src/auth.tsx index ebda74e..eef8b8f 100644 --- a/src/auth.tsx +++ b/src/auth.tsx @@ -1,19 +1,35 @@ import { Hono } from "hono"; import * as swa from "@simplewebauthn/server"; import { randomUUID } from "node:crypto"; -import { eq } from "drizzle-orm"; +import { and, eq, gt, sql } 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(); +import { getCookie, setCookie } from "hono/cookie"; +import type { Context } from "hono"; export const LoginForm = () =>
; +export async function getSession(c: Context) { + let sessionId = getCookie(c, "session"); + if (!sessionId) return null; + + let [result] = await db + .select() + .from(sessionTable) + .innerJoin(userTable, eq(userTable.id, sessionTable.userId)) + .where(({ sessions: session }) => and(eq(session.uuid, sessionId), gt(session.lastUse, sql`unixepoch() - ${60 * 60 * 24 * 7}`))); + if (!result) return null; + await db.update(sessionTable).set({ lastUse: sql`unixepoch()` }).where(eq(sessionTable.id, result.sessions.id)); + return { user: result.users, lastUse: result.sessions.lastUse, uuid: result.sessions.uuid }; +} + +let app = new Hono(); +export default app; + app.post("/register-begin", async c => { const username = randomUUID(); let options = await swa.generateRegistrationOptions({ @@ -89,5 +105,3 @@ app.post("/login-finish", async c => { 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 01b7228..339dfc0 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -1,4 +1,4 @@ -import { relations } from "drizzle-orm"; +import { relations, sql } from "drizzle-orm"; import { text, sqliteTable, integer } from "drizzle-orm/sqlite-core"; export const groupTable = sqliteTable("groups", { @@ -17,6 +17,7 @@ export const sessionTable = sqliteTable("sessions", { id: integer().primaryKey(), uuid: text().unique().notNull(), userId: integer("user_id").notNull(), + lastUse: integer("last_use", { mode: "timestamp" }).notNull().default(sql`(unixepoch())`), }); export const sessionRelations = relations(sessionTable, ({ one }) => ({ diff --git a/src/index.tsx b/src/index.tsx index e029577..5975284 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -3,7 +3,7 @@ import { Hono } from "hono"; import { createClient } from "@libsql/client"; import { drizzle } from "drizzle-orm/libsql"; import { groupTable } from "./db/schema.js"; -import authRouter, { LoginForm } from "./auth.js"; +import authRouter, { getSession, LoginForm } from "./auth.js"; export const RP_ID = "localhost"; // "uneven.0m.nu"; export const ORIGIN = `http://${RP_ID}`; @@ -38,13 +38,16 @@ app.get("/", c => c.html( )); let colors = ["red", "green", "blue"]; -app.get("/button", c => c.html( - -)); +app.get("/button", async c => { + let session = await getSession(c); + return c.html( + + ); +}); app.route("/auth", authRouter); -- cgit v1.2.3