From c076c4122b8d346abd6970ed36e1c578d3ec33c9 Mon Sep 17 00:00:00 2001 From: Felix Hennig Date: Thu, 30 Apr 2026 11:44:57 +0200 Subject: [PATCH 01/13] feat(website): replace auth-astro with better-auth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces auth-astro + @auth/core with better-auth, running in stateless mode (no database, JWT-based sessions). Removes the custom patch that was required for auth-astro TypeScript compatibility. - Add better-auth config (src/auth.ts) with GitHub OAuth and trustedProxyHeaders - Add catch-all API route at /api/auth/[...all] to handle auth requests - Replace getSession() calls across all pages and backendProxy with auth.api.getSession() - Replace signIn() in LoginButton with authClient.signIn.social() - Replace custom logout implementation with auth.api.signOut() - Update E2E test helper to use better-auth cookie name and JWT format - Remove auth-astro Astro integration from astro.config.mjs - Remove patches/auth-astro+4.2.0.patch and patch-package NOTE: session.user.id must be verified to contain the GitHub numeric user ID after a real login — see TODO in src/auth.ts. The E2E test token format is also a best-guess pending verification — see TODO in tests/helpers/auth.ts. Co-Authored-By: Claude Sonnet 4.6 --- website/astro.config.mjs | 3 +- website/package-lock.json | 683 +++++++++++------- website/package.json | 6 +- website/patches/auth-astro+4.2.0.patch | 78 -- website/src/auth.config.mjs | 32 - website/src/auth.ts | 50 ++ website/src/auth/logout.ts | 10 +- website/src/backendApi/backendProxy.ts | 5 +- website/src/components/auth/LoginButton.tsx | 20 +- website/src/components/auth/LoginState.astro | 4 +- .../src/components/auth/UserDropdown.astro | 4 +- website/src/env.d.ts | 3 - .../layouts/base/header/HamburgerMenu.astro | 4 +- website/src/pages/api/auth/[...all].ts | 5 + .../collections/[organism]/[id]/edit.astro | 4 +- .../collections/[organism]/[id]/index.astro | 4 +- .../pages/collections/[organism]/create.astro | 4 +- .../pages/collections/[organism]/index.astro | 4 +- website/src/pages/logout.astro | 6 +- website/src/pages/subscriptions/create.astro | 4 +- website/src/pages/subscriptions/index.astro | 4 +- website/tests/helpers/auth.ts | 51 +- 22 files changed, 544 insertions(+), 444 deletions(-) delete mode 100644 website/patches/auth-astro+4.2.0.patch delete mode 100644 website/src/auth.config.mjs create mode 100644 website/src/auth.ts create mode 100644 website/src/pages/api/auth/[...all].ts diff --git a/website/astro.config.mjs b/website/astro.config.mjs index 9c321540f..947bdb7c5 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -1,12 +1,11 @@ import { defineConfig } from 'astro/config'; import react from '@astrojs/react'; import node from '@astrojs/node'; -import auth from 'auth-astro'; import tailwindcss from '@tailwindcss/vite'; // https://astro.build/config export default defineConfig({ - integrations: [react(), auth({ configFile: './src/auth.config' })], + integrations: [react()], output: 'server', adapter: node({ mode: 'standalone', diff --git a/website/package-lock.json b/website/package-lock.json index 0ed3a17a2..068e39fd4 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -7,19 +7,16 @@ "": { "name": "dashboards", "version": "0.0.1", - "hasInstallScript": true, "dependencies": { "@astrojs/node": "^9.5.3", - "@auth/core": "^0.37.4", "@genspectrum/dashboard-components": "^1.17.0", "@tanstack/react-query": "^5.100.5", "astro": "^5.18.1", - "auth-astro": "^4.2.0", "axios": "^1.15.2", + "better-auth": "^1.6.9", "cookie": "^1.1.1", "dayjs": "^1.11.20", "katex": "^0.16.45", - "patch-package": "^8.0.1", "react": "^19.2.5", "react-dom": "^19.2.5", "react-katex": "^3.1.0", @@ -59,6 +56,7 @@ "eslint-plugin-import": "^2.32.0", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.2.0", + "jose": "^5.10.0", "msw": "^2.13.6", "playwright": "^1.55.0", "prettier": "^3.8.3", @@ -286,35 +284,6 @@ "yaml": "^2.8.2" } }, - "node_modules/@auth/core": { - "version": "0.37.4", - "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.37.4.tgz", - "integrity": "sha512-HOXJwXWXQRhbBDHlMU0K/6FT1v+wjtzdKhsNg0ZN7/gne6XPsIrjZ4daMcFnbq0Z/vsAbYBinQhhua0d77v7qw==", - "license": "ISC", - "dependencies": { - "@panva/hkdf": "^1.2.1", - "jose": "^5.9.6", - "oauth4webapi": "^3.1.1", - "preact": "10.24.3", - "preact-render-to-string": "6.5.11" - }, - "peerDependencies": { - "@simplewebauthn/browser": "^9.0.1", - "@simplewebauthn/server": "^9.0.2", - "nodemailer": "^6.8.0" - }, - "peerDependenciesMeta": { - "@simplewebauthn/browser": { - "optional": true - }, - "@simplewebauthn/server": { - "optional": true - }, - "nodemailer": { - "optional": true - } - } - }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -622,6 +591,147 @@ "node": ">=6.9.0" } }, + "node_modules/@better-auth/core": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@better-auth/core/-/core-1.6.9.tgz", + "integrity": "sha512-ADFk5pwmLybmc+LvYvXJ6M1x2oY/EyYLkwLuH0x28FUq12DfjL0wnE7g+WRDf3yozDO+qIxTpFGXDGwLKbfz0w==", + "license": "MIT", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.39.0", + "@standard-schema/spec": "^1.1.0", + "zod": "^4.3.6" + }, + "peerDependencies": { + "@better-auth/utils": "0.4.0", + "@better-fetch/fetch": "1.1.21", + "@cloudflare/workers-types": ">=4", + "@opentelemetry/api": "^1.9.0", + "better-call": "1.3.5", + "jose": "^6.1.0", + "kysely": "^0.28.5", + "nanostores": "^1.0.1" + }, + "peerDependenciesMeta": { + "@cloudflare/workers-types": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + } + } + }, + "node_modules/@better-auth/core/node_modules/zod": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.1.tgz", + "integrity": "sha512-a6ENMBBGZBsnlSebQ/eKCguSBeGKSf4O7BPnqVPmYGtpBYI7VSqoVqw+QcB7kPRjbqPwhYTpFbVj/RqNz/CT0Q==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@better-auth/drizzle-adapter": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@better-auth/drizzle-adapter/-/drizzle-adapter-1.6.9.tgz", + "integrity": "sha512-Lcco5hOGrMgc4XKAkvB6x72eQm4wCcya8IevMg4wBHY9W9GVg8pu23rpRX6VsVQSO4Ux13S7lFwUWtF7/r9aKw==", + "license": "MIT", + "peerDependencies": { + "@better-auth/core": "^1.6.9", + "@better-auth/utils": "0.4.0", + "drizzle-orm": "^0.45.2" + }, + "peerDependenciesMeta": { + "drizzle-orm": { + "optional": true + } + } + }, + "node_modules/@better-auth/kysely-adapter": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@better-auth/kysely-adapter/-/kysely-adapter-1.6.9.tgz", + "integrity": "sha512-gyjuuxJtZ4o9G9z9q4kqn24X2kvMSp7F+KHogYxF03SnXY/2WleAcuj57iC4wP3e9mGDbjPOrnM5K6Kr3Ktdpw==", + "license": "MIT", + "peerDependencies": { + "@better-auth/core": "^1.6.9", + "@better-auth/utils": "0.4.0", + "kysely": "^0.28.14" + }, + "peerDependenciesMeta": { + "kysely": { + "optional": true + } + } + }, + "node_modules/@better-auth/memory-adapter": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@better-auth/memory-adapter/-/memory-adapter-1.6.9.tgz", + "integrity": "sha512-XmIG4tUnOXZ+KEcWjHUjOI9Z5donD09dC2t/AQTXifAUIqx7cySg86w0KTM09ArzAxRx1fCqO36Wkt5nULnrkQ==", + "license": "MIT", + "peerDependencies": { + "@better-auth/core": "^1.6.9", + "@better-auth/utils": "0.4.0" + } + }, + "node_modules/@better-auth/mongo-adapter": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@better-auth/mongo-adapter/-/mongo-adapter-1.6.9.tgz", + "integrity": "sha512-h+AiRJ/TsBSi+ZDjySASBpbJ/9QCXBre34PSKgCz7QmTHrFM9Cg2EM4AM7LjR5lPXipEE+2rWPBc9wfnUBjhcw==", + "license": "MIT", + "peerDependencies": { + "@better-auth/core": "^1.6.9", + "@better-auth/utils": "0.4.0", + "mongodb": "^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "mongodb": { + "optional": true + } + } + }, + "node_modules/@better-auth/prisma-adapter": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@better-auth/prisma-adapter/-/prisma-adapter-1.6.9.tgz", + "integrity": "sha512-XHks01ntK20orqK/jICq8wmEbJ/zT6dct49Fk8zTQKN9QNGDc+Ix5+7z/Kvui0DXGFf790GfvRozquzaLtXa8Q==", + "license": "MIT", + "peerDependencies": { + "@better-auth/core": "^1.6.9", + "@better-auth/utils": "0.4.0", + "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", + "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "@prisma/client": { + "optional": true + }, + "prisma": { + "optional": true + } + } + }, + "node_modules/@better-auth/telemetry": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@better-auth/telemetry/-/telemetry-1.6.9.tgz", + "integrity": "sha512-0u5zkhSCAQFoN3DHvUkLHOF6MBbVTDAa6mU8mhPwiysdz1x21vMzhzfaAKN/ZGWaQ09v91/F+2qu42G/bhUV4A==", + "license": "MIT", + "peerDependencies": { + "@better-auth/core": "^1.6.9", + "@better-auth/utils": "0.4.0", + "@better-fetch/fetch": "1.1.21" + } + }, + "node_modules/@better-auth/utils": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@better-auth/utils/-/utils-0.4.0.tgz", + "integrity": "sha512-RpMtLUIQAEWMgdPLNVbIF5ON2mm+CH0U3rCdUCU1VyeAUui4m38DyK7/aXMLZov2YDjG684pS1D0MBllrmgjQA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^2.0.1" + } + }, + "node_modules/@better-fetch/fetch": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/@better-fetch/fetch/-/fetch-1.1.21.tgz", + "integrity": "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A==" + }, "node_modules/@capsizecss/unpack": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@capsizecss/unpack/-/unpack-4.0.0.tgz", @@ -2146,6 +2256,30 @@ "node": ">=18" } }, + "node_modules/@noble/ciphers": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.2.0.tgz", + "integrity": "sha512-Z6pjIZ/8IJcCGzb2S/0Px5J81yij85xASuk1teLNeg75bfT07MV3a/O2Mtn1I2se43k3lkVEcFaR10N4cgQcZA==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", + "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2209,21 +2343,21 @@ "dev": true, "license": "MIT" }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.40.0.tgz", + "integrity": "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, "node_modules/@oslojs/encoding": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz", "integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==", "license": "MIT" }, - "node_modules/@panva/hkdf": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz", - "integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, "node_modules/@pkgr/core": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", @@ -2672,6 +2806,12 @@ "node": ">=18" } }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, "node_modules/@tailwindcss/node": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.4.tgz", @@ -3301,7 +3441,7 @@ "version": "24.5.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz", "integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "undici-types": "~7.12.0" @@ -3911,12 +4051,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@yarnpkg/lockfile": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", - "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", - "license": "BSD-2-Clause" - }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -4010,6 +4144,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -5007,22 +5142,6 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, - "node_modules/auth-astro": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/auth-astro/-/auth-astro-4.2.0.tgz", - "integrity": "sha512-Jx0dFAvwJ5UJ6E/2J6rqPNhXzTX6Fj18sIq12U7n+J15TV7etpXSHFPPcQdAmfDmX3RCURp253Vxe4l/keNW1g==", - "license": "MIT", - "dependencies": { - "set-cookie-parser": "^2.5.1" - }, - "engines": { - "node": ">=17.4" - }, - "peerDependencies": { - "@auth/core": "^0.37.3", - "astro": ">=4.0.0" - } - }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -5082,6 +5201,155 @@ "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==", "license": "MIT" }, + "node_modules/better-auth": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/better-auth/-/better-auth-1.6.9.tgz", + "integrity": "sha512-EBFURtglyiEZxbx4NJBoqUD8J65dX24yC+6I9AUbIXNgUkt76mshzGbHkxZ3n/lB7Dwq3kBC+hHt0hUQsnL7HA==", + "license": "MIT", + "dependencies": { + "@better-auth/core": "1.6.9", + "@better-auth/drizzle-adapter": "1.6.9", + "@better-auth/kysely-adapter": "1.6.9", + "@better-auth/memory-adapter": "1.6.9", + "@better-auth/mongo-adapter": "1.6.9", + "@better-auth/prisma-adapter": "1.6.9", + "@better-auth/telemetry": "1.6.9", + "@better-auth/utils": "0.4.0", + "@better-fetch/fetch": "1.1.21", + "@noble/ciphers": "^2.1.1", + "@noble/hashes": "^2.0.1", + "better-call": "1.3.5", + "defu": "^6.1.4", + "jose": "^6.1.3", + "kysely": "^0.28.14", + "nanostores": "^1.1.1", + "zod": "^4.3.6" + }, + "peerDependencies": { + "@lynx-js/react": "*", + "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", + "@sveltejs/kit": "^2.0.0", + "@tanstack/react-start": "^1.0.0", + "@tanstack/solid-start": "^1.0.0", + "better-sqlite3": "^12.0.0", + "drizzle-kit": ">=0.31.4", + "drizzle-orm": "^0.45.2", + "mongodb": "^6.0.0 || ^7.0.0", + "mysql2": "^3.0.0", + "next": "^14.0.0 || ^15.0.0 || ^16.0.0", + "pg": "^8.0.0", + "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0", + "solid-js": "^1.0.0", + "svelte": "^4.0.0 || ^5.0.0", + "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0", + "vue": "^3.0.0" + }, + "peerDependenciesMeta": { + "@lynx-js/react": { + "optional": true + }, + "@prisma/client": { + "optional": true + }, + "@sveltejs/kit": { + "optional": true + }, + "@tanstack/react-start": { + "optional": true + }, + "@tanstack/solid-start": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "drizzle-kit": { + "optional": true + }, + "drizzle-orm": { + "optional": true + }, + "mongodb": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "next": { + "optional": true + }, + "pg": { + "optional": true + }, + "prisma": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "solid-js": { + "optional": true + }, + "svelte": { + "optional": true + }, + "vitest": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/better-auth/node_modules/jose": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/better-auth/node_modules/zod": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.1.tgz", + "integrity": "sha512-a6ENMBBGZBsnlSebQ/eKCguSBeGKSf4O7BPnqVPmYGtpBYI7VSqoVqw+QcB7kPRjbqPwhYTpFbVj/RqNz/CT0Q==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/better-call": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/better-call/-/better-call-1.3.5.tgz", + "integrity": "sha512-kOFJkBP7utAQLEYrobZm3vkTH8mXq5GNgvjc5/XEST1ilVHaxXUXfeDeFlqoETMtyqS4+3/h4ONX2i++ebZrvA==", + "license": "MIT", + "dependencies": { + "@better-auth/utils": "^0.4.0", + "@better-fetch/fetch": "^1.1.21", + "rou3": "^0.7.12", + "set-cookie-parser": "^3.0.1" + }, + "peerDependencies": { + "zod": "^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/better-call/node_modules/set-cookie-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.0.tgz", + "integrity": "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==", + "license": "MIT" + }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -5149,6 +5417,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -5204,6 +5473,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.0", @@ -5235,6 +5505,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -5321,6 +5592,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -5554,6 +5826,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "devOptional": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -5566,6 +5839,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "devOptional": true, "license": "MIT" }, "node_modules/color-string": { @@ -5663,6 +5937,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -5986,6 +6261,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", @@ -7259,6 +7535,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -7284,15 +7561,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/find-yarn-workspace-root": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", - "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", - "license": "Apache-2.0", - "dependencies": { - "micromatch": "^4.0.2" - } - }, "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -7417,20 +7685,6 @@ "node": ">= 0.8" } }, - "node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -7637,6 +7891,7 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, "license": "ISC" }, "node_modules/graphql": { @@ -7692,6 +7947,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -7701,6 +7957,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" @@ -8367,6 +8624,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -8577,12 +8835,14 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, "license": "ISC" }, "node_modules/iterator.prototype": { @@ -8607,7 +8867,7 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "devOptional": true, + "dev": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -8617,6 +8877,7 @@ "version": "5.10.0", "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -8667,25 +8928,6 @@ "dev": true, "license": "MIT" }, - "node_modules/json-stable-stringify": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.2.1.tgz", - "integrity": "sha512-Lp6HbbBgosLmJbjx0pBLbgvx68FaFU1sdkmBuckmhhJ88kL13OA51CDtR2yJB50eCNMH9wRqtQNNiAqQH4YXnA==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "isarray": "^2.0.5", - "jsonify": "^0.0.1", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -8713,27 +8955,6 @@ "dev": true, "license": "MIT" }, - "node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsonify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", - "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", - "license": "Public Domain", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -8785,15 +9006,6 @@ "json-buffer": "3.0.1" } }, - "node_modules/klaw-sync": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", - "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.11" - } - }, "node_modules/kleur": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", @@ -8810,6 +9022,15 @@ "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", "license": "MIT" }, + "node_modules/kysely": { + "version": "0.28.16", + "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.28.16.tgz", + "integrity": "sha512-3i5pmOiZvMDj00qhrIVbH0AnioVTx22DMP7Vn5At4yJO46iy+FM8Y/g61ltenLVSo3fiO8h8Q3QOFgf/gQ72ww==", + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/leaflet": { "version": "1.9.4", "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", @@ -8834,7 +9055,7 @@ "version": "1.32.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "devOptional": true, + "dev": true, "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -8867,6 +9088,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -8887,6 +9109,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -8907,6 +9130,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -8927,6 +9151,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -8947,6 +9172,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -8967,6 +9193,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -8987,6 +9214,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9007,6 +9235,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9027,6 +9256,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9047,6 +9277,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9067,6 +9298,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -10059,6 +10291,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -10072,6 +10305,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -10131,6 +10365,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -10286,6 +10521,21 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/nanostores": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/nanostores/-/nanostores-1.3.0.tgz", + "integrity": "sha512-XPUa/jz+P1oJvN9VBxw4L9MtdFfaH3DAryqPssqhb2kXjmb9npz0dly6rCsgFWOPr4Yg9mTfM3MDZgZZ+7A3lA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": "^20.0.0 || >=22.0.0" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -10355,15 +10605,6 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, - "node_modules/oauth4webapi": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.3.0.tgz", - "integrity": "sha512-ZlozhPlFfobzh3hB72gnBFLjXpugl/dljz1fJSRdqaV2r3D5dmi5lg2QWI0LmUYuazmE+b5exsloEv6toUtw9g==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -10399,6 +10640,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -10549,49 +10791,6 @@ "regex-recursion": "^6.0.2" } }, - "node_modules/open": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", - "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0", - "is-wsl": "^2.1.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open/node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open/node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -10784,50 +10983,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/patch-package": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.1.tgz", - "integrity": "sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw==", - "license": "MIT", - "dependencies": { - "@yarnpkg/lockfile": "^1.1.0", - "chalk": "^4.1.2", - "ci-info": "^3.7.0", - "cross-spawn": "^7.0.3", - "find-yarn-workspace-root": "^2.0.0", - "fs-extra": "^10.0.0", - "json-stable-stringify": "^1.0.2", - "klaw-sync": "^6.0.0", - "minimist": "^1.2.6", - "open": "^7.4.2", - "semver": "^7.5.3", - "slash": "^2.0.0", - "tmp": "^0.2.4", - "yaml": "^2.2.2" - }, - "bin": { - "patch-package": "index.js" - }, - "engines": { - "node": ">=14", - "npm": ">5" - } - }, - "node_modules/patch-package/node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/path-browserify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", @@ -10849,6 +11004,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -11015,15 +11171,6 @@ "url": "https://opencollective.com/preact" } }, - "node_modules/preact-render-to-string": { - "version": "6.5.11", - "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.5.11.tgz", - "integrity": "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==", - "license": "MIT", - "peerDependencies": { - "preact": ">=10" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -11782,6 +11929,12 @@ "fsevents": "~2.3.2" } }, + "node_modules/rou3": { + "version": "0.7.12", + "resolved": "https://registry.npmjs.org/rou3/-/rou3-0.7.12.tgz", + "integrity": "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==", + "license": "MIT" + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -11991,16 +12144,11 @@ "integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==", "license": "ISC" }, - "node_modules/set-cookie-parser": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", - "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", - "license": "MIT" - }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", @@ -12098,6 +12246,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -12110,6 +12259,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -12258,15 +12408,6 @@ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "license": "MIT" }, - "node_modules/slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/smol-toml": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.1.tgz", @@ -12586,6 +12727,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -12784,19 +12926,11 @@ "dev": true, "license": "MIT" }, - "node_modules/tmp": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", - "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", - "license": "MIT", - "engines": { - "node": ">=14.14" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -13059,6 +13193,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -13143,7 +13278,7 @@ "version": "7.12.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz", "integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/unified": { @@ -13299,15 +13434,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/until-async": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/until-async/-/until-async-3.0.2.tgz", @@ -13921,6 +14047,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" diff --git a/website/package.json b/website/package.json index 676792286..04e51f1c8 100644 --- a/website/package.json +++ b/website/package.json @@ -14,7 +14,6 @@ "check-types": "astro sync && tsc --noEmit", "check-astro": "astro check", "check-lint": "eslint .", - "postinstall": "patch-package", "test": "vitest", "test:node": "vitest --project node", "test:headed": "vitest --browser.headless false", @@ -26,16 +25,14 @@ }, "dependencies": { "@astrojs/node": "^9.5.3", - "@auth/core": "^0.37.4", "@genspectrum/dashboard-components": "^1.17.0", "@tanstack/react-query": "^5.100.5", "astro": "^5.18.1", - "auth-astro": "^4.2.0", + "better-auth": "^1.6.9", "axios": "^1.15.2", "cookie": "^1.1.1", "dayjs": "^1.11.20", "katex": "^0.16.45", - "patch-package": "^8.0.1", "react": "^19.2.5", "react-dom": "^19.2.5", "react-katex": "^3.1.0", @@ -71,6 +68,7 @@ "daisyui": "^5.5.19", "dotenv": "^16.5.0", "eslint": "^9.39.2", + "jose": "^5.10.0", "eslint-plugin-astro": "^1.7.0", "eslint-plugin-import": "^2.32.0", "eslint-plugin-react": "^7.37.5", diff --git a/website/patches/auth-astro+4.2.0.patch b/website/patches/auth-astro+4.2.0.patch deleted file mode 100644 index 1e24bd246..000000000 --- a/website/patches/auth-astro+4.2.0.patch +++ /dev/null @@ -1,78 +0,0 @@ -diff --git a/node_modules/auth-astro/client.ts b/node_modules/auth-astro/client.ts -index 8a30c9e..d853a86 100644 ---- a/node_modules/auth-astro/client.ts -+++ b/node_modules/auth-astro/client.ts -@@ -1,11 +1,43 @@ --import type { -- LiteralUnion, -- SignInOptions, -- SignInAuthorizationParams, -- SignOutParams, --} from 'next-auth/react' - import type { BuiltInProviderType, RedirectableProviderType } from '@auth/core/providers' - -+// manually imported from next-auth/react to get the proper type without installing the dependency ourselves -+// from https://github.com/nextauthjs/next-auth/blob/9411046efb21f83218df4d7d7da6639e100adb2a/packages/next-auth/src/lib/client.ts -+export interface SignInOptions -+ extends Record { -+ /** @deprecated Use `redirectTo` instead. */ -+ callbackUrl?: string -+ /** -+ * Specify where the user should be redirected to after a successful signin. -+ * -+ * By default, it is the page the sign-in was initiated from. -+ */ -+ redirectTo?: string -+ /** -+ * You might want to deal with the signin response on the same page, instead of redirecting to another page. -+ * For example, if an error occurs (like wrong credentials given by the user), you might want to show an inline error message on the input field. -+ * -+ * For this purpose, you can set this to option `redirect: false`. -+ */ -+ redirect?: Redirect -+} -+export interface SignOutParams { -+ /** @deprecated Use `redirectTo` instead. */ -+ callbackUrl?: string -+ /** -+ * If you pass `redirect: false`, the page will not reload. -+ * The session will be deleted, and `useSession` is notified, so any indication about the user will be shown as logged out automatically. -+ * It can give a very nice experience for the user. -+ */ -+ redirectTo?: string -+ /** [Documentation](https://next-auth.js.org/getting-started/client#using-the-redirect-false-option-1 */ -+ redirect?: Redirect -+} -+export type SignInAuthorizationParams = -+ | string -+ | string[][] -+ | Record -+ | URLSearchParams -+ - interface AstroSignInOptions extends SignInOptions { - /** The base path for authentication (default: /api/auth) */ - prefix?: string -@@ -24,9 +56,7 @@ interface AstroSignOutParams extends SignOutParams { - * [Documentation](https://authjs.dev/reference/utilities/#signin) - */ - export async function signIn

( -- providerId?: LiteralUnion< -- P extends RedirectableProviderType ? P | BuiltInProviderType : BuiltInProviderType -- >, -+ providerId?: BuiltInProviderType, - options?: AstroSignInOptions, - authorizationParams?: SignInAuthorizationParams - ) { -diff --git a/node_modules/auth-astro/src/integration.ts b/node_modules/auth-astro/src/integration.ts -index 04262ae..5b19bc6 100644 ---- a/node_modules/auth-astro/src/integration.ts -+++ b/node_modules/auth-astro/src/integration.ts -@@ -34,7 +34,7 @@ export default (config: AstroAuthConfig = {}): AstroIntegration => ({ - logger.error('No Adapter found, please make sure you provide one in your Astro config') - } - const edge = ['@astrojs/vercel/edge', '@astrojs/cloudflare'].includes( -- astroConfig.adapter.name -+ astroConfig.adapter!.name - ) - - if ( diff --git a/website/src/auth.config.mjs b/website/src/auth.config.mjs deleted file mode 100644 index 49ca9c167..000000000 --- a/website/src/auth.config.mjs +++ /dev/null @@ -1,32 +0,0 @@ -import GitHub from '@auth/core/providers/github'; -import { defineConfig } from 'auth-astro'; - -import { getGitHubClientId, getGitHubClientSecret } from './config'; - -export default defineConfig({ - secret: process.env.AUTH_SECRET, - callbacks: { - session: async ({ session, token }) => { - session.user.id = token.userIdFromProvider; - return session; - }, - jwt: ({ profile, token }) => { - if (!profile) { - return token; - } - - // This step in necessary, since @auth/core overwrites the id from the provider with a random UUID - // The id is then extracted in the session callback and assigned to the user object - return { - ...token, - userIdFromProvider: profile.id, - }; - }, - }, - providers: [ - GitHub({ - clientId: getGitHubClientId(), - clientSecret: getGitHubClientSecret(), - }), - ], -}); diff --git a/website/src/auth.ts b/website/src/auth.ts new file mode 100644 index 000000000..e9dec2c6e --- /dev/null +++ b/website/src/auth.ts @@ -0,0 +1,50 @@ +import { betterAuth } from 'better-auth'; + +import { getGitHubClientId, getGitHubClientSecret } from './config'; + +export const auth = betterAuth({ + // TODO - maybe we can check again if this is read automatically? Should be, according to the docs. + secret: process.env.AUTH_SECRET, + socialProviders: { + github: { + clientId: getGitHubClientId(), + clientSecret: getGitHubClientSecret(), + // TODO: Verify that session.user.id contains the GitHub numeric user ID, not a generated UUID. + // + // BACKGROUND: + // The old auth-astro setup used two callbacks to work around @auth/core overwriting the + // GitHub user ID with a random UUID: + // - jwt callback: stashed profile.id (the real GitHub numeric ID) as token.userIdFromProvider + // - session callback: copied token.userIdFromProvider onto session.user.id + // This ensured session.user.id === "" (e.g. "12345678"). + // + // RISK: + // In better-auth's stateless mode (no database), session.user.id may be a generated ID + // rather than the GitHub numeric ID. This would break backend API calls, because + // backendProxy.ts passes session.user.id as the `userId` query parameter, and the backend + // uses that ID for ownership checks (e.g. who owns a collection). If the ID is wrong, + // users will be unable to edit or delete their own resources. + // + // HOW TO VERIFY: + // Sign in with GitHub, then log session.user.id in backendProxy.ts and check whether it + // matches your GitHub numeric account ID (visible at https://api.github.com/users/). + // + // WORKAROUND (if session.user.id is not the GitHub ID): + // Add a mapProfileToUser function here to explicitly set the user ID from the provider profile: + // + // mapProfileToUser: (profile) => ({ + // id: String(profile.id), + // name: profile.name, + // email: profile.email, + // image: profile.avatar_url, + // }), + // + // First verify that `mapProfileToUser` is the correct option name by checking the type + // definitions in node_modules/better-auth/dist/types.d.ts, as the API may differ from + // what is described in community examples. + }, + }, + advanced: { + trustedProxyHeaders: true, + }, +}); diff --git a/website/src/auth/logout.ts b/website/src/auth/logout.ts index 2796b3d46..aa93e8b46 100644 --- a/website/src/auth/logout.ts +++ b/website/src/auth/logout.ts @@ -1,11 +1,5 @@ -import { Auth, raw, skipCSRFCheck } from '@auth/core'; -import type { ResponseInternal } from '@auth/core/types'; -import authConfig from 'auth:config'; - -export type LogoutResponse = Required>; +import { auth } from '../auth'; export async function logout(request: Request) { - const url = new URL(`${authConfig.prefix}/signout`, request.url); - const authRequest = new Request(url, { headers: request.headers, method: 'POST' }); - return (await Auth(authRequest, { ...authConfig, raw, skipCSRFCheck })) as LogoutResponse; + return auth.api.signOut({ headers: request.headers }); } diff --git a/website/src/backendApi/backendProxy.ts b/website/src/backendApi/backendProxy.ts index 0cb6ab975..48aea80ff 100644 --- a/website/src/backendApi/backendProxy.ts +++ b/website/src/backendApi/backendProxy.ts @@ -1,6 +1,5 @@ -import { getSession } from 'auth-astro/server'; - import { getBackendHost } from '../config.ts'; +import { auth } from '../auth.ts'; import { getInstanceLogger } from '../logger.ts'; import type { ProblemDetail } from '../types/ProblemDetail.ts'; import { getErrorLogMessage } from '../util/getErrorLogMessage.ts'; @@ -15,7 +14,7 @@ const API_PATHNAME_LENGTH = '/api'.length; * in here, instead of in the backend. */ export async function proxyToBackend({ request }: { request: Request }): Promise { - const session = await getSession(request); + const session = await auth.api.getSession({ headers: request.headers }); if (session?.user?.id === undefined) { return getUnauthorizedResponse(request.url); diff --git a/website/src/components/auth/LoginButton.tsx b/website/src/components/auth/LoginButton.tsx index be35bb329..542849c44 100644 --- a/website/src/components/auth/LoginButton.tsx +++ b/website/src/components/auth/LoginButton.tsx @@ -1,10 +1,11 @@ -import { signIn } from 'auth-astro/client'; +import { createAuthClient } from 'better-auth/client'; import { getClientLogger } from '../../clientLogger.ts'; import { getErrorLogMessage } from '../../util/getErrorLogMessage.ts'; import { useErrorToast } from '../ErrorReportInstruction.tsx'; const logger = getClientLogger('LoginButton'); +const authClient = createAuthClient(); export function LoginButton() { const { showErrorToast } = useErrorToast(logger); @@ -13,13 +14,18 @@ export function LoginButton() { const callbackUrlThatDoesNotImmediatelyLogoutAgain = new URL(window.location.href).pathname.endsWith('/logout') ? new URL('/', window.location.href).toString() : undefined; - signIn('github', { callbackUrl: callbackUrlThatDoesNotImmediatelyLogoutAgain }).catch((error: unknown) => { - showErrorToast({ - error: error instanceof Error ? error : new Error(String(error)), - logMessage: `Login failed: ${getErrorLogMessage(error)}`, - errorToastMessages: ['Login failed. Please try again.'], + authClient.signIn + .social({ + provider: 'github', + callbackURL: callbackUrlThatDoesNotImmediatelyLogoutAgain, + }) + .catch((error: unknown) => { + showErrorToast({ + error: error instanceof Error ? error : new Error(String(error)), + logMessage: `Login failed: ${getErrorLogMessage(error)}`, + errorToastMessages: ['Login failed. Please try again.'], + }); }); - }); }; return ( diff --git a/website/src/components/auth/LoginState.astro b/website/src/components/auth/LoginState.astro index eabed68c2..8f3a815d5 100644 --- a/website/src/components/auth/LoginState.astro +++ b/website/src/components/auth/LoginState.astro @@ -1,5 +1,5 @@ --- -import { getSession } from 'auth-astro/server'; +import { auth } from '../../auth'; import { LoginButton } from './LoginButton'; import UserDropdown from './UserDropdown.astro'; @@ -10,7 +10,7 @@ interface Props { const { forceLoggedOutState = false } = Astro.props; -const session = await getSession(Astro.request); +const session = await auth.api.getSession({ headers: Astro.request.headers }); const showLoggedInState = !forceLoggedOutState && session?.user !== undefined; --- diff --git a/website/src/components/auth/UserDropdown.astro b/website/src/components/auth/UserDropdown.astro index 7722d01ee..3b27971f0 100644 --- a/website/src/components/auth/UserDropdown.astro +++ b/website/src/components/auth/UserDropdown.astro @@ -1,10 +1,8 @@ --- -import type { Session } from '@auth/core/types'; - import { isStaging } from '../../config'; interface Props { - session: Session; + session: { user: { name: string | null } }; } const { session } = Astro.props; diff --git a/website/src/env.d.ts b/website/src/env.d.ts index 48c2d463e..bb89362c2 100644 --- a/website/src/env.d.ts +++ b/website/src/env.d.ts @@ -1,8 +1,5 @@ /// /// -// declare module 'set-cookie-parser' is added, because tsc checks our dependency auth-astro/server, -// which uses set-cookie-parser, and it doesn't have types. -declare module 'set-cookie-parser'; interface ImportMetaEnv { // eslint-disable-next-line @typescript-eslint/naming-convention diff --git a/website/src/layouts/base/header/HamburgerMenu.astro b/website/src/layouts/base/header/HamburgerMenu.astro index 25333f21a..ad1173ac9 100644 --- a/website/src/layouts/base/header/HamburgerMenu.astro +++ b/website/src/layouts/base/header/HamburgerMenu.astro @@ -1,5 +1,5 @@ --- -import { getSession } from 'auth-astro/server'; +import { auth } from '../../../auth'; import HamburgerMenuItem from './HamburgerMenuItem.astro'; import HamburgerMenuSection from './HamburgerMenuSection.astro'; @@ -16,7 +16,7 @@ const { forceLoggedOutState } = Astro.props; const pathogenMegaMenuSections = Object.values(getPathogenMegaMenuSections()); -const session = await getSession(Astro.request); +const session = await auth.api.getSession({ headers: Astro.request.headers }); const showLoggedInState = !forceLoggedOutState && session?.user !== undefined; const showSubscriptions = isStaging(); --- diff --git a/website/src/pages/api/auth/[...all].ts b/website/src/pages/api/auth/[...all].ts new file mode 100644 index 000000000..27c01830c --- /dev/null +++ b/website/src/pages/api/auth/[...all].ts @@ -0,0 +1,5 @@ +import type { APIRoute } from 'astro'; + +import { auth } from '../../../auth'; + +export const ALL: APIRoute = ({ request }) => auth.handler(request); diff --git a/website/src/pages/collections/[organism]/[id]/edit.astro b/website/src/pages/collections/[organism]/[id]/edit.astro index 89a8b903b..ae816df17 100644 --- a/website/src/pages/collections/[organism]/[id]/edit.astro +++ b/website/src/pages/collections/[organism]/[id]/edit.astro @@ -1,5 +1,5 @@ --- -import { getSession } from 'auth-astro/server'; +import { auth } from '../../../../auth'; import { BackendError, BackendService } from '../../../../backendApi/backendService.ts'; import NotLoggedIn from '../../../../components/auth/NotLoggedIn.astro'; @@ -26,7 +26,7 @@ if (id === undefined) { } const orgConfig = organismConfig[parsedOrganism.data]; -const session = await getSession(Astro.request); +const session = await auth.api.getSession({ headers: Astro.request.headers }); const config = getDashboardsConfig(); let collection; diff --git a/website/src/pages/collections/[organism]/[id]/index.astro b/website/src/pages/collections/[organism]/[id]/index.astro index 5e986d58c..b0213d166 100644 --- a/website/src/pages/collections/[organism]/[id]/index.astro +++ b/website/src/pages/collections/[organism]/[id]/index.astro @@ -1,5 +1,5 @@ --- -import { getSession } from 'auth-astro/server'; +import { auth } from '../../../../auth'; import { BackendService, BackendError } from '../../../../backendApi/backendService.ts'; import { CollectionDetail } from '../../../../components/collections/detail/CollectionDetail'; @@ -28,7 +28,7 @@ if (id === undefined) { const orgConfig = organismConfig[parsedOrganism.data]; const lapisConfig = getOrganismConfig(parsedOrganism.data).lapis; -const session = await getSession(Astro.request); +const session = await auth.api.getSession({ headers: Astro.request.headers }); // GitHub user IDs are numbers, but ownedBy is stored as a string in the backend. const currentUserId = session?.user?.id !== undefined ? String(session.user.id) : undefined; diff --git a/website/src/pages/collections/[organism]/create.astro b/website/src/pages/collections/[organism]/create.astro index c80e87b65..c6438c474 100644 --- a/website/src/pages/collections/[organism]/create.astro +++ b/website/src/pages/collections/[organism]/create.astro @@ -1,5 +1,5 @@ --- -import { getSession } from 'auth-astro/server'; +import { auth } from '../../../auth'; import NotLoggedIn from '../../../components/auth/NotLoggedIn.astro'; import { CollectionCreate } from '../../../components/collections/create/CollectionCreate'; @@ -17,7 +17,7 @@ if (!parsedOrganism.success) { } const orgConfig = organismConfig[parsedOrganism.data]; -const session = await getSession(Astro.request); +const session = await auth.api.getSession({ headers: Astro.request.headers }); const config = getDashboardsConfig(); --- diff --git a/website/src/pages/collections/[organism]/index.astro b/website/src/pages/collections/[organism]/index.astro index 7357ef53d..22e39d153 100644 --- a/website/src/pages/collections/[organism]/index.astro +++ b/website/src/pages/collections/[organism]/index.astro @@ -1,5 +1,5 @@ --- -import { getSession } from 'auth-astro/server'; +import { auth } from '../../../auth'; import { CollectionsOverview } from '../../../components/collections/overview/CollectionsOverview'; import { isStaging } from '../../../config'; @@ -21,7 +21,7 @@ if (!parsedOrganism.success) { } const orgConfig = organismConfig[parsedOrganism.data]; -const session = await getSession(Astro.request); +const session = await auth.api.getSession({ headers: Astro.request.headers }); const isLoggedIn = session?.user?.id !== undefined; --- diff --git a/website/src/pages/logout.astro b/website/src/pages/logout.astro index 4d1143bf9..cd94c19ee 100644 --- a/website/src/pages/logout.astro +++ b/website/src/pages/logout.astro @@ -2,10 +2,10 @@ import { logout } from '../auth/logout'; import BaseLayout from '../layouts/base/BaseLayout.astro'; -const { cookies } = await logout(Astro.request); +const signOutResponse = await logout(Astro.request); -for (const cookie of cookies) { - Astro.cookies.delete(cookie.name); +for (const cookie of signOutResponse.headers.getSetCookie()) { + Astro.response.headers.append('Set-Cookie', cookie); } --- diff --git a/website/src/pages/subscriptions/create.astro b/website/src/pages/subscriptions/create.astro index 6a8cfa8ca..8bef5380e 100644 --- a/website/src/pages/subscriptions/create.astro +++ b/website/src/pages/subscriptions/create.astro @@ -1,5 +1,5 @@ --- -import { getSession } from 'auth-astro/server'; +import { auth } from '../../auth'; import NotLoggedIn from '../../components/auth/NotLoggedIn.astro'; import { SubscriptionsCreate } from '../../components/subscriptions/create/SubscriptionsCreate'; @@ -7,7 +7,7 @@ import { getDashboardsConfig } from '../../config'; import { defaultBreadcrumbs } from '../../layouts/Breadcrumbs'; import SubscriptionPageLayout from '../../layouts/ContaineredPage/ContaineredPageLayout.astro'; -const session = await getSession(Astro.request); +const session = await auth.api.getSession({ headers: Astro.request.headers }); const config = getDashboardsConfig(); --- diff --git a/website/src/pages/subscriptions/index.astro b/website/src/pages/subscriptions/index.astro index 38947dcd8..37ca36baf 100644 --- a/website/src/pages/subscriptions/index.astro +++ b/website/src/pages/subscriptions/index.astro @@ -1,5 +1,5 @@ --- -import { getSession } from 'auth-astro/server'; +import { auth } from '../../auth'; import NotLoggedIn from '../../components/auth/NotLoggedIn.astro'; import { Subscriptions } from '../../components/subscriptions/overview/Subscriptions'; @@ -8,7 +8,7 @@ import SubscriptionPageLayout from '../../layouts/ContaineredPage/ContaineredPag import { organismsSchema } from '../../types/Organism'; import { Page } from '../../types/pages'; -const session = await getSession(Astro.request); +const session = await auth.api.getSession({ headers: Astro.request.headers }); const organisms = organismsSchema.parse(Astro.url.searchParams.getAll('organism')); diff --git a/website/tests/helpers/auth.ts b/website/tests/helpers/auth.ts index b053d2704..42a5d8049 100644 --- a/website/tests/helpers/auth.ts +++ b/website/tests/helpers/auth.ts @@ -1,7 +1,8 @@ -import { encode } from '@auth/core/jwt'; import { type Page } from '@playwright/test'; +import { SignJWT } from 'jose'; -const COOKIE_NAME = 'authjs.session-token'; +// Cookie name used by better-auth for session tokens +const COOKIE_NAME = 'better-auth.session_token'; export async function setupAuthCookie(page: Page, userId: string) { const authSecret = process.env.AUTH_SECRET; @@ -9,11 +10,47 @@ export async function setupAuthCookie(page: Page, userId: string) { throw new Error('AUTH_SECRET environment variable is not set'); } - const token = await encode({ - token: { userIdFromProvider: userId, name: userId }, - secret: authSecret, - salt: COOKIE_NAME, - }); + // TODO: This token creation is a best guess at the format better-auth uses in stateless mode + // and MUST be verified before relying on it in CI or production test runs. + // + // BACKGROUND: + // The previous implementation used @auth/core/jwt's encode() function, which had a well-known + // format (a signed JWE/JWT with a specific salt). better-auth's internal token format is not + // publicly documented, and may differ significantly — it may not be a plain JWT at all, may use + // a different signing algorithm, a different payload structure, or additional envelope fields. + // + // WHAT THIS ASSUMES: + // - better-auth uses HS256-signed JWTs in stateless mode + // - The payload shape is { id, userId, expiresAt, user: { id, name } } + // - The secret is used directly (encoded as UTF-8) to sign the token + // - The cookie name is "better-auth.session_token" + // These are all reasonable guesses but none are guaranteed to be correct. + // + // HOW TO VERIFY: + // 1. Run the app locally and sign in with GitHub + // 2. Inspect the "better-auth.session_token" cookie in your browser's DevTools + // 3. Decode the token at jwt.io (if it is a JWT) to see the actual payload shape and header + // 4. Compare against what this function produces and adjust accordingly + // + // ALTERNATIVES IF THIS APPROACH DOESN'T WORK: + // - Check if better-auth exposes internal utilities for creating test sessions + // (search for test helpers in node_modules/better-auth/dist/) + // - Drive the full GitHub OAuth flow in tests using a mock OAuth server + // - Replace the cookie-based approach with a test-only API endpoint that creates a session + // directly via auth.api (only enabled in test/dev environments) + const secret = new TextEncoder().encode(authSecret); + const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); + + const token = await new SignJWT({ + id: userId, + userId, + expiresAt: expiresAt.toISOString(), + user: { id: userId, name: userId }, + }) + .setProtectedHeader({ alg: 'HS256' }) + .setIssuedAt() + .setExpirationTime(expiresAt) + .sign(secret); await page.context().addCookies([ { From 9f2c91d0f874eba242d98c89c8c727314b38423f Mon Sep 17 00:00:00 2001 From: Felix Hennig Date: Thu, 30 Apr 2026 13:51:16 +0200 Subject: [PATCH 02/13] fix(website): pass asResponse: true to better-auth signOut auth.api.signOut() returns typed data, not a Response object, so calling .headers.getSetCookie() on it threw a 500. The asResponse option makes it return a proper Response with Set-Cookie headers. Co-Authored-By: Claude Sonnet 4.6 --- website/src/auth/logout.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/src/auth/logout.ts b/website/src/auth/logout.ts index aa93e8b46..5e08da10e 100644 --- a/website/src/auth/logout.ts +++ b/website/src/auth/logout.ts @@ -1,5 +1,5 @@ import { auth } from '../auth'; export async function logout(request: Request) { - return auth.api.signOut({ headers: request.headers }); + return auth.api.signOut({ headers: request.headers, asResponse: true }); } From f320c6461cf728a4f7de4ba3e533697f3f5b9b05 Mon Sep 17 00:00:00 2001 From: Felix Hennig Date: Mon, 4 May 2026 09:45:23 +0200 Subject: [PATCH 03/13] fix(website): resolve GitHub user ID from account instead of session better-auth's session.user.id is a randomly generated internal ID, not the GitHub numeric ID the backend uses for ownership checks. Adds a getGitHubUserId() helper that retrieves the correct ID via auth.api.listUserAccounts() and updates backendProxy and the two collection pages that do ownership comparisons. Co-Authored-By: Claude Sonnet 4.6 --- website/src/auth/getGitHubUserId.ts | 20 +++++++++++++++++++ website/src/backendApi/backendProxy.ts | 8 ++++---- .../collections/[organism]/[id]/edit.astro | 10 +++++----- .../collections/[organism]/[id]/index.astro | 6 ++---- 4 files changed, 31 insertions(+), 13 deletions(-) create mode 100644 website/src/auth/getGitHubUserId.ts diff --git a/website/src/auth/getGitHubUserId.ts b/website/src/auth/getGitHubUserId.ts new file mode 100644 index 000000000..d490d8188 --- /dev/null +++ b/website/src/auth/getGitHubUserId.ts @@ -0,0 +1,20 @@ +import { auth } from '../auth'; + +/** + * Returns the GitHub numeric user ID for the currently authenticated user, or undefined if the + * user is not logged in. + * + * better-auth stores an internal randomly generated ID on `session.user.id`, which is not the + * GitHub user ID. The real GitHub ID is stored as `accountId` on the linked account record. + * This function retrieves it via `listUserAccounts` and finds the account with providerId 'github'. + * + * This ID is used as the `userId` query parameter in backend API calls, where it is used for + * ownership checks (e.g. whether a user can edit or delete a collection). + */ +export async function getGitHubUserId(headers: Headers): Promise { + const session = await auth.api.getSession({ headers }); + if (!session) return undefined; + + const accounts = await auth.api.listUserAccounts({ headers }); + return accounts.find((a) => a.providerId === 'github')?.accountId; +} diff --git a/website/src/backendApi/backendProxy.ts b/website/src/backendApi/backendProxy.ts index 48aea80ff..1496f71dc 100644 --- a/website/src/backendApi/backendProxy.ts +++ b/website/src/backendApi/backendProxy.ts @@ -1,5 +1,5 @@ import { getBackendHost } from '../config.ts'; -import { auth } from '../auth.ts'; +import { getGitHubUserId } from '../auth/getGitHubUserId.ts'; import { getInstanceLogger } from '../logger.ts'; import type { ProblemDetail } from '../types/ProblemDetail.ts'; import { getErrorLogMessage } from '../util/getErrorLogMessage.ts'; @@ -14,13 +14,13 @@ const API_PATHNAME_LENGTH = '/api'.length; * in here, instead of in the backend. */ export async function proxyToBackend({ request }: { request: Request }): Promise { - const session = await auth.api.getSession({ headers: request.headers }); + const userId = await getGitHubUserId(request.headers); - if (session?.user?.id === undefined) { + if (userId === undefined) { return getUnauthorizedResponse(request.url); } - return proxyRequest(request, session.user.id); + return proxyRequest(request, userId); } /** diff --git a/website/src/pages/collections/[organism]/[id]/edit.astro b/website/src/pages/collections/[organism]/[id]/edit.astro index ae816df17..40181ed13 100644 --- a/website/src/pages/collections/[organism]/[id]/edit.astro +++ b/website/src/pages/collections/[organism]/[id]/edit.astro @@ -1,5 +1,5 @@ --- -import { auth } from '../../../../auth'; +import { getGitHubUserId } from '../../../../auth/getGitHubUserId'; import { BackendError, BackendService } from '../../../../backendApi/backendService.ts'; import NotLoggedIn from '../../../../components/auth/NotLoggedIn.astro'; @@ -26,14 +26,14 @@ if (id === undefined) { } const orgConfig = organismConfig[parsedOrganism.data]; -const session = await auth.api.getSession({ headers: Astro.request.headers }); +const currentUserId = await getGitHubUserId(Astro.request.headers); const config = getDashboardsConfig(); let collection; -if (session?.user?.id !== undefined) { +if (currentUserId !== undefined) { try { collection = await new BackendService(getBackendHost()).getCollection({ id }); - if (String(session.user.id) !== collection.ownedBy) { + if (currentUserId !== collection.ownedBy) { return Astro.redirect('/404'); } } catch (error) { @@ -59,7 +59,7 @@ const collectionTitle = collection !== undefined ? `#${id} ${collection.name}` : ]} > { - session?.user?.id !== undefined ? ( + currentUserId !== undefined ? ( Date: Mon, 4 May 2026 12:50:39 +0200 Subject: [PATCH 04/13] feat(website): centralise auth in Astro middleware, store GitHub ID as user field Replaces per-page auth calls with a single middleware that runs once per request and populates Astro.locals. The GitHub numeric user ID is now stored as a dedicated `githubId` additionalField on the user object via `mapProfileToUser`, so it is available directly on `session.user` without any secondary lookups. - Add `authMiddleware` that calls `getSession` once and sets `context.locals.user` and `context.locals.session` - Configure `user.additionalFields.githubId` + `mapProfileToUser` in `auth.ts` to populate it from the GitHub profile at login time - Remove `getGitHubUserId` helper (no longer needed) - Update `backendProxy` to read `context.locals.user?.githubId` via `APIContext` instead of re-deriving the ID from headers - Update all pages/components to read from `Astro.locals` instead of calling `auth.api.getSession` directly - Update `App.Locals` types to use the inferred auth user type so `githubId` is typed correctly everywhere - Remove unused `jose` devDependency and E2E test auth helper Co-Authored-By: Claude Sonnet 4.6 --- website/package.json | 3 +- website/src/auth.ts | 45 ++++--------- website/src/auth/getGitHubUserId.ts | 20 ------ website/src/backendApi/backendProxy.ts | 15 +++-- website/src/components/auth/LoginState.astro | 7 +- website/src/env.d.ts | 13 ++++ .../layouts/base/header/HamburgerMenu.astro | 7 +- website/src/middleware.ts | 4 +- website/src/middleware/authMiddleware.ts | 19 ++++++ .../collections/[organism]/[id]/edit.astro | 4 +- .../collections/[organism]/[id]/index.astro | 4 +- .../pages/collections/[organism]/create.astro | 5 +- .../pages/collections/[organism]/index.astro | 5 +- website/src/pages/subscriptions/create.astro | 6 +- website/src/pages/subscriptions/index.astro | 6 +- website/tests/helpers/auth.ts | 65 ------------------- 16 files changed, 65 insertions(+), 163 deletions(-) delete mode 100644 website/src/auth/getGitHubUserId.ts create mode 100644 website/src/middleware/authMiddleware.ts delete mode 100644 website/tests/helpers/auth.ts diff --git a/website/package.json b/website/package.json index 04e51f1c8..4c778354f 100644 --- a/website/package.json +++ b/website/package.json @@ -28,8 +28,8 @@ "@genspectrum/dashboard-components": "^1.17.0", "@tanstack/react-query": "^5.100.5", "astro": "^5.18.1", - "better-auth": "^1.6.9", "axios": "^1.15.2", + "better-auth": "^1.6.9", "cookie": "^1.1.1", "dayjs": "^1.11.20", "katex": "^0.16.45", @@ -68,7 +68,6 @@ "daisyui": "^5.5.19", "dotenv": "^16.5.0", "eslint": "^9.39.2", - "jose": "^5.10.0", "eslint-plugin-astro": "^1.7.0", "eslint-plugin-import": "^2.32.0", "eslint-plugin-react": "^7.37.5", diff --git a/website/src/auth.ts b/website/src/auth.ts index e9dec2c6e..eb91c46ae 100644 --- a/website/src/auth.ts +++ b/website/src/auth.ts @@ -5,43 +5,22 @@ import { getGitHubClientId, getGitHubClientSecret } from './config'; export const auth = betterAuth({ // TODO - maybe we can check again if this is read automatically? Should be, according to the docs. secret: process.env.AUTH_SECRET, + user: { + additionalFields: { + githubId: { + type: "string", + input: false + } + } + }, socialProviders: { github: { clientId: getGitHubClientId(), clientSecret: getGitHubClientSecret(), - // TODO: Verify that session.user.id contains the GitHub numeric user ID, not a generated UUID. - // - // BACKGROUND: - // The old auth-astro setup used two callbacks to work around @auth/core overwriting the - // GitHub user ID with a random UUID: - // - jwt callback: stashed profile.id (the real GitHub numeric ID) as token.userIdFromProvider - // - session callback: copied token.userIdFromProvider onto session.user.id - // This ensured session.user.id === "" (e.g. "12345678"). - // - // RISK: - // In better-auth's stateless mode (no database), session.user.id may be a generated ID - // rather than the GitHub numeric ID. This would break backend API calls, because - // backendProxy.ts passes session.user.id as the `userId` query parameter, and the backend - // uses that ID for ownership checks (e.g. who owns a collection). If the ID is wrong, - // users will be unable to edit or delete their own resources. - // - // HOW TO VERIFY: - // Sign in with GitHub, then log session.user.id in backendProxy.ts and check whether it - // matches your GitHub numeric account ID (visible at https://api.github.com/users/). - // - // WORKAROUND (if session.user.id is not the GitHub ID): - // Add a mapProfileToUser function here to explicitly set the user ID from the provider profile: - // - // mapProfileToUser: (profile) => ({ - // id: String(profile.id), - // name: profile.name, - // email: profile.email, - // image: profile.avatar_url, - // }), - // - // First verify that `mapProfileToUser` is the correct option name by checking the type - // definitions in node_modules/better-auth/dist/types.d.ts, as the API may differ from - // what is described in community examples. + // store the GitHub user ID (i.e. 45882389) in the 'user' object, + // which is easy to access from context later on. + // the 'id' property cannot be overwritten. + mapProfileToUser: (profile) => ({ githubId: profile.id }) }, }, advanced: { diff --git a/website/src/auth/getGitHubUserId.ts b/website/src/auth/getGitHubUserId.ts deleted file mode 100644 index d490d8188..000000000 --- a/website/src/auth/getGitHubUserId.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { auth } from '../auth'; - -/** - * Returns the GitHub numeric user ID for the currently authenticated user, or undefined if the - * user is not logged in. - * - * better-auth stores an internal randomly generated ID on `session.user.id`, which is not the - * GitHub user ID. The real GitHub ID is stored as `accountId` on the linked account record. - * This function retrieves it via `listUserAccounts` and finds the account with providerId 'github'. - * - * This ID is used as the `userId` query parameter in backend API calls, where it is used for - * ownership checks (e.g. whether a user can edit or delete a collection). - */ -export async function getGitHubUserId(headers: Headers): Promise { - const session = await auth.api.getSession({ headers }); - if (!session) return undefined; - - const accounts = await auth.api.listUserAccounts({ headers }); - return accounts.find((a) => a.providerId === 'github')?.accountId; -} diff --git a/website/src/backendApi/backendProxy.ts b/website/src/backendApi/backendProxy.ts index 1496f71dc..c6ec6923f 100644 --- a/website/src/backendApi/backendProxy.ts +++ b/website/src/backendApi/backendProxy.ts @@ -1,5 +1,6 @@ +import type { APIContext } from 'astro'; + import { getBackendHost } from '../config.ts'; -import { getGitHubUserId } from '../auth/getGitHubUserId.ts'; import { getInstanceLogger } from '../logger.ts'; import type { ProblemDetail } from '../types/ProblemDetail.ts'; import { getErrorLogMessage } from '../util/getErrorLogMessage.ts'; @@ -13,21 +14,21 @@ const API_PATHNAME_LENGTH = '/api'.length; * This proxying through the frontend server is used, so we do the user login handling * in here, instead of in the backend. */ -export async function proxyToBackend({ request }: { request: Request }): Promise { - const userId = await getGitHubUserId(request.headers); +export async function proxyToBackend(context: APIContext): Promise { + const userId = context.locals.user?.githubId; if (userId === undefined) { - return getUnauthorizedResponse(request.url); + return getUnauthorizedResponse(context.request.url); } - return proxyRequest(request, userId); + return proxyRequest(context.request, userId); } /** * Proxies the request to the backend without any user ID, regardless of login state. */ -export async function proxyToBackendNoAuth({ request }: { request: Request }): Promise { - return proxyRequest(request, undefined); +export async function proxyToBackendNoAuth(context: APIContext): Promise { + return proxyRequest(context.request, undefined); } async function proxyRequest(request: Request, userId: string | undefined): Promise { diff --git a/website/src/components/auth/LoginState.astro b/website/src/components/auth/LoginState.astro index 8f3a815d5..676aa913d 100644 --- a/website/src/components/auth/LoginState.astro +++ b/website/src/components/auth/LoginState.astro @@ -1,6 +1,4 @@ --- -import { auth } from '../../auth'; - import { LoginButton } from './LoginButton'; import UserDropdown from './UserDropdown.astro'; @@ -10,13 +8,12 @@ interface Props { const { forceLoggedOutState = false } = Astro.props; -const session = await auth.api.getSession({ headers: Astro.request.headers }); -const showLoggedInState = !forceLoggedOutState && session?.user !== undefined; +const showLoggedInState = !forceLoggedOutState && Astro.locals.user !== null; --- { showLoggedInState ? ( - + ) : (

diff --git a/website/src/env.d.ts b/website/src/env.d.ts index bb89362c2..18d0283e0 100644 --- a/website/src/env.d.ts +++ b/website/src/env.d.ts @@ -1,6 +1,19 @@ /// /// +declare namespace App { + // Note: 'import {} from ""' syntax does not work in .d.ts files. + // We derive the User type from the auth config so that additionalFields (e.g. githubId) are included. + type AuthUser = NonNullable< + Awaited> + >['user']; + + interface Locals { + user: AuthUser | null; + session: import('better-auth').Session | null; + } +} + interface ImportMetaEnv { // eslint-disable-next-line @typescript-eslint/naming-convention readonly DASHBOARDS_ENVIRONMENT: 'dashboards-staging' | 'dashboards-prod'; diff --git a/website/src/layouts/base/header/HamburgerMenu.astro b/website/src/layouts/base/header/HamburgerMenu.astro index ad1173ac9..b814e76cd 100644 --- a/website/src/layouts/base/header/HamburgerMenu.astro +++ b/website/src/layouts/base/header/HamburgerMenu.astro @@ -1,6 +1,4 @@ --- -import { auth } from '../../../auth'; - import HamburgerMenuItem from './HamburgerMenuItem.astro'; import HamburgerMenuSection from './HamburgerMenuSection.astro'; import { getPathogenMegaMenuSections } from './getPathogenMegaMenuSections'; @@ -16,8 +14,7 @@ const { forceLoggedOutState } = Astro.props; const pathogenMegaMenuSections = Object.values(getPathogenMegaMenuSections()); -const session = await auth.api.getSession({ headers: Astro.request.headers }); -const showLoggedInState = !forceLoggedOutState && session?.user !== undefined; +const showLoggedInState = !forceLoggedOutState && Astro.locals.user !== null; const showSubscriptions = isStaging(); --- @@ -44,7 +41,7 @@ const showSubscriptions = isStaging(); showLoggedInState ? ( { + const session = await auth.api.getSession({ headers: context.request.headers }); + + if (session) { + context.locals.user = session.user; + context.locals.session = session.session; + } else { + context.locals.user = null; + context.locals.session = null; + } + + return next(); +}); diff --git a/website/src/pages/collections/[organism]/[id]/edit.astro b/website/src/pages/collections/[organism]/[id]/edit.astro index 40181ed13..f981da0d2 100644 --- a/website/src/pages/collections/[organism]/[id]/edit.astro +++ b/website/src/pages/collections/[organism]/[id]/edit.astro @@ -1,6 +1,4 @@ --- -import { getGitHubUserId } from '../../../../auth/getGitHubUserId'; - import { BackendError, BackendService } from '../../../../backendApi/backendService.ts'; import NotLoggedIn from '../../../../components/auth/NotLoggedIn.astro'; import { CollectionEdit } from '../../../../components/collections/edit/CollectionEdit'; @@ -26,7 +24,7 @@ if (id === undefined) { } const orgConfig = organismConfig[parsedOrganism.data]; -const currentUserId = await getGitHubUserId(Astro.request.headers); +const currentUserId = Astro.locals.user?.githubId; const config = getDashboardsConfig(); let collection; diff --git a/website/src/pages/collections/[organism]/[id]/index.astro b/website/src/pages/collections/[organism]/[id]/index.astro index 255d1ccb8..90338e683 100644 --- a/website/src/pages/collections/[organism]/[id]/index.astro +++ b/website/src/pages/collections/[organism]/[id]/index.astro @@ -1,6 +1,4 @@ --- -import { getGitHubUserId } from '../../../../auth/getGitHubUserId'; - import { BackendService, BackendError } from '../../../../backendApi/backendService.ts'; import { CollectionDetail } from '../../../../components/collections/detail/CollectionDetail'; import { getBackendHost, getOrganismConfig } from '../../../../config.ts'; @@ -28,7 +26,7 @@ if (id === undefined) { const orgConfig = organismConfig[parsedOrganism.data]; const lapisConfig = getOrganismConfig(parsedOrganism.data).lapis; -const currentUserId = await getGitHubUserId(Astro.request.headers); +const currentUserId = Astro.locals.user?.githubId; let collection: Collection | undefined; diff --git a/website/src/pages/collections/[organism]/create.astro b/website/src/pages/collections/[organism]/create.astro index c6438c474..59319a295 100644 --- a/website/src/pages/collections/[organism]/create.astro +++ b/website/src/pages/collections/[organism]/create.astro @@ -1,6 +1,4 @@ --- -import { auth } from '../../../auth'; - import NotLoggedIn from '../../../components/auth/NotLoggedIn.astro'; import { CollectionCreate } from '../../../components/collections/create/CollectionCreate'; import { getDashboardsConfig } from '../../../config'; @@ -17,7 +15,6 @@ if (!parsedOrganism.success) { } const orgConfig = organismConfig[parsedOrganism.data]; -const session = await auth.api.getSession({ headers: Astro.request.headers }); const config = getDashboardsConfig(); --- @@ -31,7 +28,7 @@ const config = getDashboardsConfig(); ]} > { - session?.user?.id !== undefined ? ( + Astro.locals.user !== null ? ( ) : ( diff --git a/website/src/pages/collections/[organism]/index.astro b/website/src/pages/collections/[organism]/index.astro index 22e39d153..bb98bb7ba 100644 --- a/website/src/pages/collections/[organism]/index.astro +++ b/website/src/pages/collections/[organism]/index.astro @@ -1,6 +1,4 @@ --- -import { auth } from '../../../auth'; - import { CollectionsOverview } from '../../../components/collections/overview/CollectionsOverview'; import { isStaging } from '../../../config'; import { defaultBreadcrumbs } from '../../../layouts/Breadcrumbs'; @@ -21,8 +19,7 @@ if (!parsedOrganism.success) { } const orgConfig = organismConfig[parsedOrganism.data]; -const session = await auth.api.getSession({ headers: Astro.request.headers }); -const isLoggedIn = session?.user?.id !== undefined; +const isLoggedIn = Astro.locals.user !== null; --- - {session?.user?.id !== undefined ? : } + {Astro.locals.user !== null ? : } diff --git a/website/src/pages/subscriptions/index.astro b/website/src/pages/subscriptions/index.astro index 37ca36baf..19c7a7e62 100644 --- a/website/src/pages/subscriptions/index.astro +++ b/website/src/pages/subscriptions/index.astro @@ -1,6 +1,4 @@ --- -import { auth } from '../../auth'; - import NotLoggedIn from '../../components/auth/NotLoggedIn.astro'; import { Subscriptions } from '../../components/subscriptions/overview/Subscriptions'; import { defaultBreadcrumbs } from '../../layouts/Breadcrumbs'; @@ -8,8 +6,6 @@ import SubscriptionPageLayout from '../../layouts/ContaineredPage/ContaineredPag import { organismsSchema } from '../../types/Organism'; import { Page } from '../../types/pages'; -const session = await auth.api.getSession({ headers: Astro.request.headers }); - const organisms = organismsSchema.parse(Astro.url.searchParams.getAll('organism')); const breadcrumbs = [ @@ -22,5 +18,5 @@ const breadcrumbs = [ --- - {session?.user?.id !== undefined ? : } + {Astro.locals.user !== null ? : } diff --git a/website/tests/helpers/auth.ts b/website/tests/helpers/auth.ts deleted file mode 100644 index 42a5d8049..000000000 --- a/website/tests/helpers/auth.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { type Page } from '@playwright/test'; -import { SignJWT } from 'jose'; - -// Cookie name used by better-auth for session tokens -const COOKIE_NAME = 'better-auth.session_token'; - -export async function setupAuthCookie(page: Page, userId: string) { - const authSecret = process.env.AUTH_SECRET; - if (authSecret === undefined) { - throw new Error('AUTH_SECRET environment variable is not set'); - } - - // TODO: This token creation is a best guess at the format better-auth uses in stateless mode - // and MUST be verified before relying on it in CI or production test runs. - // - // BACKGROUND: - // The previous implementation used @auth/core/jwt's encode() function, which had a well-known - // format (a signed JWE/JWT with a specific salt). better-auth's internal token format is not - // publicly documented, and may differ significantly — it may not be a plain JWT at all, may use - // a different signing algorithm, a different payload structure, or additional envelope fields. - // - // WHAT THIS ASSUMES: - // - better-auth uses HS256-signed JWTs in stateless mode - // - The payload shape is { id, userId, expiresAt, user: { id, name } } - // - The secret is used directly (encoded as UTF-8) to sign the token - // - The cookie name is "better-auth.session_token" - // These are all reasonable guesses but none are guaranteed to be correct. - // - // HOW TO VERIFY: - // 1. Run the app locally and sign in with GitHub - // 2. Inspect the "better-auth.session_token" cookie in your browser's DevTools - // 3. Decode the token at jwt.io (if it is a JWT) to see the actual payload shape and header - // 4. Compare against what this function produces and adjust accordingly - // - // ALTERNATIVES IF THIS APPROACH DOESN'T WORK: - // - Check if better-auth exposes internal utilities for creating test sessions - // (search for test helpers in node_modules/better-auth/dist/) - // - Drive the full GitHub OAuth flow in tests using a mock OAuth server - // - Replace the cookie-based approach with a test-only API endpoint that creates a session - // directly via auth.api (only enabled in test/dev environments) - const secret = new TextEncoder().encode(authSecret); - const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); - - const token = await new SignJWT({ - id: userId, - userId, - expiresAt: expiresAt.toISOString(), - user: { id: userId, name: userId }, - }) - .setProtectedHeader({ alg: 'HS256' }) - .setIssuedAt() - .setExpirationTime(expiresAt) - .sign(secret); - - await page.context().addCookies([ - { - name: COOKIE_NAME, - value: token, - domain: 'localhost', - path: '/', - httpOnly: true, - sameSite: 'Lax', - }, - ]); -} From c3a592f7d855cd893e3f9ab616c68aed579e2a32 Mon Sep 17 00:00:00 2001 From: Felix Hennig Date: Mon, 4 May 2026 14:26:47 +0200 Subject: [PATCH 05/13] feat(website): add E2E auth cookie helper and set cookie prefix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds tests/helpers/auth.ts with createAuthCookies() and setupAuthCookie() that craft valid better-auth session cookies for Playwright tests without needing a real OAuth flow. Also sets cookiePrefix to 'gen-spectrum' in auth config (required for the helper to match what the server sets). Note: E2E cookie injection is untested — cookieCache also needs to be enabled in auth.ts before getSession will accept the crafted session_data cookie without a store lookup. Co-Authored-By: Claude Sonnet 4.6 --- website/src/auth.ts | 1 + website/tests/helpers/auth.ts | 80 +++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 website/tests/helpers/auth.ts diff --git a/website/src/auth.ts b/website/src/auth.ts index eb91c46ae..ca6c4023e 100644 --- a/website/src/auth.ts +++ b/website/src/auth.ts @@ -25,5 +25,6 @@ export const auth = betterAuth({ }, advanced: { trustedProxyHeaders: true, + cookiePrefix: 'gen-spectrum', }, }); diff --git a/website/tests/helpers/auth.ts b/website/tests/helpers/auth.ts new file mode 100644 index 000000000..55e6853b9 --- /dev/null +++ b/website/tests/helpers/auth.ts @@ -0,0 +1,80 @@ +import { makeSignature, symmetricEncodeJWT } from 'better-auth/crypto'; +import type { Cookie, Page } from '@playwright/test'; + +// NOTE: for getSession to use the session_data cookie (bypassing the in-memory store lookup), +// cookieCache must be enabled in auth.ts: +// session: { cookieCache: { enabled: true, maxAge: 60 * 60, strategy: 'jwe' } } + +const SECRET = process.env.AUTH_SECRET ?? ''; +const COOKIE_PREFIX = 'gen-spectrum'; +const DOMAIN = 'localhost'; + +const COOKIE_BASE = { + domain: DOMAIN, + path: '/', + httpOnly: true, + secure: false, + sameSite: 'Lax' as const, +}; + +export async function createAuthCookies( + accountPayload: Record, + sessionPayload: Record, +): Promise { + const token = crypto.randomUUID(); + const signedToken = `${token}.${await makeSignature(token, SECRET)}`; + const accountData = await symmetricEncodeJWT(accountPayload, SECRET, 'better-auth-account'); + const sessionData = await symmetricEncodeJWT(sessionPayload, SECRET, 'better-auth-session'); + + return [ + { ...COOKIE_BASE, name: `${COOKIE_PREFIX}.session_token`, value: signedToken }, + { ...COOKIE_BASE, name: `${COOKIE_PREFIX}.account_data`, value: accountData }, + { ...COOKIE_BASE, name: `${COOKIE_PREFIX}.session_data`, value: sessionData }, + ]; +} + +export async function setupAuthCookie(page: Page, name: string): Promise { + const now = new Date(); + const expiresAt = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000); + const userId = crypto.randomUUID(); + const sessionToken = crypto.randomUUID(); + + const cookies = await createAuthCookies( + { + id: crypto.randomUUID(), + providerId: 'github', + accountId: '1234567', + userId, + accessToken: 'e2e-test-access-token', + scope: 'read:user,user:email', + createdAt: now.toISOString(), + updatedAt: now.toISOString(), + }, + { + session: { + id: crypto.randomUUID(), + userId, + token: sessionToken, + expiresAt: expiresAt.toISOString(), + createdAt: now.toISOString(), + updatedAt: now.toISOString(), + ipAddress: null, + userAgent: null, + }, + user: { + id: userId, + name, + email: 'e2e@test.local', + emailVerified: true, + image: null, + createdAt: now.toISOString(), + updatedAt: now.toISOString(), + githubId: '1234567', + }, + updatedAt: now.getTime(), + version: '1', + }, + ); + + await page.context().addCookies(cookies); +} From 00bacccd35a8106e7d4260c348818263d6a2a688 Mon Sep 17 00:00:00 2001 From: Felix Hennig Date: Mon, 4 May 2026 14:31:16 +0200 Subject: [PATCH 06/13] format --- website/package-lock.json | 148 +++++++++---------- website/src/auth.ts | 10 +- website/src/components/auth/LoginState.astro | 7 +- website/src/env.d.ts | 4 +- website/tests/helpers/auth.ts | 2 +- 5 files changed, 85 insertions(+), 86 deletions(-) diff --git a/website/package-lock.json b/website/package-lock.json index 068e39fd4..b64a40e5e 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -56,7 +56,6 @@ "eslint-plugin-import": "^2.32.0", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.2.0", - "jose": "^5.10.0", "msw": "^2.13.6", "playwright": "^1.55.0", "prettier": "^3.8.3", @@ -124,7 +123,8 @@ "version": "2.13.1", "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-2.13.1.tgz", "integrity": "sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@astrojs/internal-helpers": { "version": "0.7.5", @@ -315,6 +315,7 @@ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -596,6 +597,7 @@ "resolved": "https://registry.npmjs.org/@better-auth/core/-/core-1.6.9.tgz", "integrity": "sha512-ADFk5pwmLybmc+LvYvXJ6M1x2oY/EyYLkwLuH0x28FUq12DfjL0wnE7g+WRDf3yozDO+qIxTpFGXDGwLKbfz0w==", "license": "MIT", + "peer": true, "dependencies": { "@opentelemetry/semantic-conventions": "^1.39.0", "@standard-schema/spec": "^1.1.0", @@ -723,6 +725,7 @@ "resolved": "https://registry.npmjs.org/@better-auth/utils/-/utils-0.4.0.tgz", "integrity": "sha512-RpMtLUIQAEWMgdPLNVbIF5ON2mm+CH0U3rCdUCU1VyeAUui4m38DyK7/aXMLZov2YDjG684pS1D0MBllrmgjQA==", "license": "MIT", + "peer": true, "dependencies": { "@noble/hashes": "^2.0.1" } @@ -730,7 +733,8 @@ "node_modules/@better-fetch/fetch": { "version": "1.1.21", "resolved": "https://registry.npmjs.org/@better-fetch/fetch/-/fetch-1.1.21.tgz", - "integrity": "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A==" + "integrity": "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A==", + "peer": true }, "node_modules/@capsizecss/unpack": { "version": "4.0.0", @@ -3216,6 +3220,7 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -3355,7 +3360,7 @@ "version": "5.2.2", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/deep-eql": "*" @@ -3374,7 +3379,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/estree": { @@ -3441,8 +3446,9 @@ "version": "24.5.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz", "integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==", - "dev": true, + "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.12.0" } @@ -3453,6 +3459,7 @@ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -3463,6 +3470,7 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "dev": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -3567,6 +3575,7 @@ "integrity": "sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.59.1", "@typescript-eslint/types": "8.59.1", @@ -3808,6 +3817,7 @@ "integrity": "sha512-tJxiPrWmzH8a+w9nLKlQMzAKX/7VjFs50MWgcAj7p9XQ7AQ9/35fByFYptgPELyLw+0aixTnC4pUWV+APcZ/kw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@testing-library/dom": "^10.4.0", "@testing-library/user-event": "^14.6.1", @@ -3842,7 +3852,7 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/chai": "^5.2.2", @@ -3859,7 +3869,7 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@vitest/spy": "3.2.4", @@ -3886,7 +3896,7 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "tinyrainbow": "^2.0.0" @@ -3899,7 +3909,7 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@vitest/utils": "3.2.4", @@ -3914,7 +3924,7 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@vitest/pretty-format": "3.2.4", @@ -3929,7 +3939,7 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "tinyspy": "^4.0.3" @@ -3942,7 +3952,7 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@vitest/pretty-format": "3.2.4", @@ -4056,6 +4066,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4371,7 +4382,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=12" @@ -4382,6 +4393,7 @@ "resolved": "https://registry.npmjs.org/astro/-/astro-5.18.1.tgz", "integrity": "sha512-m4VWilWZ+Xt6NPoYzC4CgGZim/zQUO7WFL0RHCH0AiEavF1153iC3+me2atDvXpf/yX4PyGUeD8wZLq1cirT3g==", "license": "MIT", + "peer": true, "dependencies": { "@astrojs/compiler": "^2.13.0", "@astrojs/internal-helpers": "0.7.6", @@ -5306,15 +5318,6 @@ } } }, - "node_modules/better-auth/node_modules/jose": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", - "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, "node_modules/better-auth/node_modules/zod": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.1.tgz", @@ -5329,6 +5332,7 @@ "resolved": "https://registry.npmjs.org/better-call/-/better-call-1.3.5.tgz", "integrity": "sha512-kOFJkBP7utAQLEYrobZm3vkTH8mXq5GNgvjc5/XEST1ilVHaxXUXfeDeFlqoETMtyqS4+3/h4ONX2i++ebZrvA==", "license": "MIT", + "peer": true, "dependencies": { "@better-auth/utils": "^0.4.0", "@better-fetch/fetch": "^1.1.21", @@ -5446,6 +5450,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001737", "electron-to-chromium": "^1.5.211", @@ -5463,7 +5468,7 @@ "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -5575,7 +5580,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "assertion-error": "^2.0.1", @@ -5640,6 +5645,7 @@ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz", "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", "license": "MIT", + "peer": true, "dependencies": { "@kurkle/color": "^0.3.0" }, @@ -5672,7 +5678,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 16" @@ -6100,16 +6106,6 @@ "node": ">=12" } }, - "node_modules/d3-selection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", - "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", - "license": "ISC", - "optional": true, - "engines": { - "node": ">=12" - } - }, "node_modules/d3-timer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", @@ -6244,7 +6240,7 @@ "version": "5.0.2", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6" @@ -6868,6 +6864,7 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -7362,7 +7359,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz", "integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=12.0.0" @@ -8867,18 +8864,18 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" } }, "node_modules/jose": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", - "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==", - "dev": true, + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/panva" } @@ -9027,6 +9024,7 @@ "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.28.16.tgz", "integrity": "sha512-3i5pmOiZvMDj00qhrIVbH0AnioVTx22DMP7Vn5At4yJO46iy+FM8Y/g61ltenLVSo3fiO8h8Q3QOFgf/gQ72ww==", "license": "MIT", + "peer": true, "engines": { "node": ">=20.0.0" } @@ -9055,7 +9053,7 @@ "version": "1.32.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "dev": true, + "devOptional": true, "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -9088,7 +9086,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9109,7 +9106,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9130,7 +9126,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9151,7 +9146,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9172,7 +9166,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9193,7 +9186,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9214,7 +9206,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9235,7 +9226,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9256,7 +9246,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9277,7 +9266,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9298,7 +9286,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9409,7 +9396,7 @@ "version": "3.1.4", "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.4.tgz", "integrity": "sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/lru-cache": { @@ -10532,6 +10519,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": "^20.0.0 || >=22.0.0" } @@ -11028,14 +11016,14 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/pathval": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 14.16" @@ -11187,6 +11175,7 @@ "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -11203,6 +11192,7 @@ "integrity": "sha512-RiBETaaP9veVstE4vUwSIcdATj6dKmXljouXc/DDNwBSPTp8FRkLGDSGFClKsAFeeg+13SB0Z1JZvbD76bigJw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@astrojs/compiler": "^2.9.1", "prettier": "^3.0.0", @@ -11362,6 +11352,7 @@ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -11444,6 +11435,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -11453,6 +11445,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -11896,6 +11889,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.9.tgz", "integrity": "sha512-nF5XYqWWp9hx/LrpC8sZvvvmq0TeTjQgaZHYmAgwysT9nh8sWnZhBnM8ZyVbbJFIQBLwHDNoMqsBZBbUo4U8sQ==", "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.6" }, @@ -12361,7 +12355,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/signal-exit": { @@ -12452,7 +12446,7 @@ "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/statuses": { @@ -12468,7 +12462,7 @@ "version": "3.9.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/stop-iteration-iterator": { @@ -12697,7 +12691,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "js-tokens": "^9.0.1" @@ -12710,7 +12704,7 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/suf-log": { @@ -12818,7 +12812,8 @@ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.4.tgz", "integrity": "sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tapable": { "version": "2.3.3", @@ -12850,14 +12845,14 @@ "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/tinyexec": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/tinyglobby": { @@ -12880,7 +12875,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": "^18.0.0 || >=20.0.0" @@ -12890,7 +12885,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=14.0.0" @@ -12900,7 +12895,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz", "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=14.0.0" @@ -13193,8 +13188,8 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -13278,7 +13273,7 @@ "version": "7.12.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz", "integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/unified": { @@ -13551,6 +13546,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -13624,7 +13620,7 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "cac": "^6.7.14", @@ -13680,8 +13676,9 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", - "dev": true, + "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -14161,7 +14158,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "siginfo": "^2.0.0", @@ -14194,6 +14191,7 @@ "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", "license": "MIT", + "peer": true, "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.8", @@ -14371,6 +14369,7 @@ "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -14531,6 +14530,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/website/src/auth.ts b/website/src/auth.ts index ca6c4023e..ec3a92b14 100644 --- a/website/src/auth.ts +++ b/website/src/auth.ts @@ -8,10 +8,10 @@ export const auth = betterAuth({ user: { additionalFields: { githubId: { - type: "string", - input: false - } - } + type: 'string', + input: false, + }, + }, }, socialProviders: { github: { @@ -20,7 +20,7 @@ export const auth = betterAuth({ // store the GitHub user ID (i.e. 45882389) in the 'user' object, // which is easy to access from context later on. // the 'id' property cannot be overwritten. - mapProfileToUser: (profile) => ({ githubId: profile.id }) + mapProfileToUser: (profile) => ({ githubId: profile.id }), }, }, advanced: { diff --git a/website/src/components/auth/LoginState.astro b/website/src/components/auth/LoginState.astro index 676aa913d..992c4a6c6 100644 --- a/website/src/components/auth/LoginState.astro +++ b/website/src/components/auth/LoginState.astro @@ -8,12 +8,13 @@ interface Props { const { forceLoggedOutState = false } = Astro.props; -const showLoggedInState = !forceLoggedOutState && Astro.locals.user !== null; +const user = Astro.locals.user; +const showLoggedInState = !forceLoggedOutState && user !== null; --- { - showLoggedInState ? ( - + showLoggedInState && user ? ( + ) : (
diff --git a/website/src/env.d.ts b/website/src/env.d.ts index 18d0283e0..c104ca02b 100644 --- a/website/src/env.d.ts +++ b/website/src/env.d.ts @@ -4,9 +4,7 @@ declare namespace App { // Note: 'import {} from ""' syntax does not work in .d.ts files. // We derive the User type from the auth config so that additionalFields (e.g. githubId) are included. - type AuthUser = NonNullable< - Awaited> - >['user']; + type AuthUser = NonNullable>>['user']; interface Locals { user: AuthUser | null; diff --git a/website/tests/helpers/auth.ts b/website/tests/helpers/auth.ts index 55e6853b9..7f64d6b22 100644 --- a/website/tests/helpers/auth.ts +++ b/website/tests/helpers/auth.ts @@ -1,5 +1,5 @@ -import { makeSignature, symmetricEncodeJWT } from 'better-auth/crypto'; import type { Cookie, Page } from '@playwright/test'; +import { makeSignature, symmetricEncodeJWT } from 'better-auth/crypto'; // NOTE: for getSession to use the session_data cookie (bypassing the in-memory store lookup), // cookieCache must be enabled in auth.ts: From c78f7ad91b1888d33b4213ce6346136d255ac033 Mon Sep 17 00:00:00 2001 From: Felix Hennig Date: Mon, 4 May 2026 14:39:24 +0200 Subject: [PATCH 07/13] asdfasdf --- website/package-lock.json | 140 +++++++++++++++++++------------------- 1 file changed, 69 insertions(+), 71 deletions(-) diff --git a/website/package-lock.json b/website/package-lock.json index b64a40e5e..fec4326c8 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -123,8 +123,7 @@ "version": "2.13.1", "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-2.13.1.tgz", "integrity": "sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@astrojs/internal-helpers": { "version": "0.7.5", @@ -315,7 +314,6 @@ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -597,7 +595,6 @@ "resolved": "https://registry.npmjs.org/@better-auth/core/-/core-1.6.9.tgz", "integrity": "sha512-ADFk5pwmLybmc+LvYvXJ6M1x2oY/EyYLkwLuH0x28FUq12DfjL0wnE7g+WRDf3yozDO+qIxTpFGXDGwLKbfz0w==", "license": "MIT", - "peer": true, "dependencies": { "@opentelemetry/semantic-conventions": "^1.39.0", "@standard-schema/spec": "^1.1.0", @@ -725,7 +722,6 @@ "resolved": "https://registry.npmjs.org/@better-auth/utils/-/utils-0.4.0.tgz", "integrity": "sha512-RpMtLUIQAEWMgdPLNVbIF5ON2mm+CH0U3rCdUCU1VyeAUui4m38DyK7/aXMLZov2YDjG684pS1D0MBllrmgjQA==", "license": "MIT", - "peer": true, "dependencies": { "@noble/hashes": "^2.0.1" } @@ -733,8 +729,7 @@ "node_modules/@better-fetch/fetch": { "version": "1.1.21", "resolved": "https://registry.npmjs.org/@better-fetch/fetch/-/fetch-1.1.21.tgz", - "integrity": "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A==", - "peer": true + "integrity": "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A==" }, "node_modules/@capsizecss/unpack": { "version": "4.0.0", @@ -3220,7 +3215,6 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -3360,7 +3354,7 @@ "version": "5.2.2", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@types/deep-eql": "*" @@ -3379,7 +3373,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@types/estree": { @@ -3446,9 +3440,8 @@ "version": "24.5.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz", "integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==", - "devOptional": true, + "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.12.0" } @@ -3459,7 +3452,6 @@ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -3470,7 +3462,6 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -3575,7 +3566,6 @@ "integrity": "sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.59.1", "@typescript-eslint/types": "8.59.1", @@ -3817,7 +3807,6 @@ "integrity": "sha512-tJxiPrWmzH8a+w9nLKlQMzAKX/7VjFs50MWgcAj7p9XQ7AQ9/35fByFYptgPELyLw+0aixTnC4pUWV+APcZ/kw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@testing-library/dom": "^10.4.0", "@testing-library/user-event": "^14.6.1", @@ -3852,7 +3841,7 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@types/chai": "^5.2.2", @@ -3869,7 +3858,7 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@vitest/spy": "3.2.4", @@ -3896,7 +3885,7 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "tinyrainbow": "^2.0.0" @@ -3909,7 +3898,7 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@vitest/utils": "3.2.4", @@ -3924,7 +3913,7 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@vitest/pretty-format": "3.2.4", @@ -3939,7 +3928,7 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "tinyspy": "^4.0.3" @@ -3952,7 +3941,7 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@vitest/pretty-format": "3.2.4", @@ -4066,7 +4055,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4382,7 +4370,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -4393,7 +4381,6 @@ "resolved": "https://registry.npmjs.org/astro/-/astro-5.18.1.tgz", "integrity": "sha512-m4VWilWZ+Xt6NPoYzC4CgGZim/zQUO7WFL0RHCH0AiEavF1153iC3+me2atDvXpf/yX4PyGUeD8wZLq1cirT3g==", "license": "MIT", - "peer": true, "dependencies": { "@astrojs/compiler": "^2.13.0", "@astrojs/internal-helpers": "0.7.6", @@ -5318,6 +5305,15 @@ } } }, + "node_modules/better-auth/node_modules/jose": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/better-auth/node_modules/zod": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.1.tgz", @@ -5332,7 +5328,6 @@ "resolved": "https://registry.npmjs.org/better-call/-/better-call-1.3.5.tgz", "integrity": "sha512-kOFJkBP7utAQLEYrobZm3vkTH8mXq5GNgvjc5/XEST1ilVHaxXUXfeDeFlqoETMtyqS4+3/h4ONX2i++ebZrvA==", "license": "MIT", - "peer": true, "dependencies": { "@better-auth/utils": "^0.4.0", "@better-fetch/fetch": "^1.1.21", @@ -5450,7 +5445,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001737", "electron-to-chromium": "^1.5.211", @@ -5468,7 +5462,7 @@ "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5580,7 +5574,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "assertion-error": "^2.0.1", @@ -5645,7 +5639,6 @@ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz", "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", "license": "MIT", - "peer": true, "dependencies": { "@kurkle/color": "^0.3.0" }, @@ -5678,7 +5671,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 16" @@ -6106,6 +6099,16 @@ "node": ">=12" } }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "optional": true, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-timer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", @@ -6240,7 +6243,7 @@ "version": "5.0.2", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -6864,7 +6867,6 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -7359,7 +7361,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz", "integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=12.0.0" @@ -8864,7 +8866,7 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "devOptional": true, + "dev": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -8875,7 +8877,6 @@ "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/panva" } @@ -9024,7 +9025,6 @@ "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.28.16.tgz", "integrity": "sha512-3i5pmOiZvMDj00qhrIVbH0AnioVTx22DMP7Vn5At4yJO46iy+FM8Y/g61ltenLVSo3fiO8h8Q3QOFgf/gQ72ww==", "license": "MIT", - "peer": true, "engines": { "node": ">=20.0.0" } @@ -9053,7 +9053,7 @@ "version": "1.32.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "devOptional": true, + "dev": true, "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -9086,6 +9086,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9106,6 +9107,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9126,6 +9128,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9146,6 +9149,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9166,6 +9170,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9186,6 +9191,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9206,6 +9212,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9226,6 +9233,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9246,6 +9254,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9266,6 +9275,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9286,6 +9296,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9396,7 +9407,7 @@ "version": "3.1.4", "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.4.tgz", "integrity": "sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/lru-cache": { @@ -10519,7 +10530,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": "^20.0.0 || >=22.0.0" } @@ -11016,14 +11026,14 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/pathval": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 14.16" @@ -11175,7 +11185,6 @@ "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -11192,7 +11201,6 @@ "integrity": "sha512-RiBETaaP9veVstE4vUwSIcdATj6dKmXljouXc/DDNwBSPTp8FRkLGDSGFClKsAFeeg+13SB0Z1JZvbD76bigJw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@astrojs/compiler": "^2.9.1", "prettier": "^3.0.0", @@ -11352,7 +11360,6 @@ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -11435,7 +11442,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -11445,7 +11451,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -11889,7 +11894,6 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.9.tgz", "integrity": "sha512-nF5XYqWWp9hx/LrpC8sZvvvmq0TeTjQgaZHYmAgwysT9nh8sWnZhBnM8ZyVbbJFIQBLwHDNoMqsBZBbUo4U8sQ==", "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.6" }, @@ -12355,7 +12359,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/signal-exit": { @@ -12446,7 +12450,7 @@ "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/statuses": { @@ -12462,7 +12466,7 @@ "version": "3.9.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/stop-iteration-iterator": { @@ -12691,7 +12695,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "js-tokens": "^9.0.1" @@ -12704,7 +12708,7 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/suf-log": { @@ -12812,8 +12816,7 @@ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.4.tgz", "integrity": "sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tapable": { "version": "2.3.3", @@ -12845,14 +12848,14 @@ "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/tinyexec": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/tinyglobby": { @@ -12875,7 +12878,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": "^18.0.0 || >=20.0.0" @@ -12885,7 +12888,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=14.0.0" @@ -12895,7 +12898,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz", "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=14.0.0" @@ -13188,8 +13191,8 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -13273,7 +13276,7 @@ "version": "7.12.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz", "integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/unified": { @@ -13546,7 +13549,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -13620,7 +13622,7 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "cac": "^6.7.14", @@ -13676,9 +13678,8 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", - "devOptional": true, + "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -14158,7 +14159,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "siginfo": "^2.0.0", @@ -14191,7 +14192,6 @@ "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", "license": "MIT", - "peer": true, "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.8", @@ -14369,7 +14369,6 @@ "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -14530,7 +14529,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } From 326cbab92a2fdd9e3a890c4a84277f91c91c32bb Mon Sep 17 00:00:00 2001 From: Felix Hennig Date: Mon, 4 May 2026 15:43:17 +0200 Subject: [PATCH 08/13] fix check-types --- website/tests/helpers/auth.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/website/tests/helpers/auth.ts b/website/tests/helpers/auth.ts index 7f64d6b22..f8cb67be4 100644 --- a/website/tests/helpers/auth.ts +++ b/website/tests/helpers/auth.ts @@ -15,6 +15,7 @@ const COOKIE_BASE = { httpOnly: true, secure: false, sameSite: 'Lax' as const, + expires: -1, }; export async function createAuthCookies( From 3a7c7182c2495c0525621d4e724d5262262b6639 Mon Sep 17 00:00:00 2001 From: Felix Hennig Date: Mon, 4 May 2026 16:01:16 +0200 Subject: [PATCH 09/13] fix(website): remove patches dir from Dockerfile COPY The patches/ directory was deleted when auth-astro was replaced with better-auth, causing the Docker build to fail. Co-Authored-By: Claude Sonnet 4.6 --- Dockerfile_website | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile_website b/Dockerfile_website index 823df38e9..194ebfc2a 100644 --- a/Dockerfile_website +++ b/Dockerfile_website @@ -3,7 +3,7 @@ FROM node:24-alpine AS base WORKDIR /app -COPY website/package.json website/package-lock.json website/patches ./ +COPY website/package.json website/package-lock.json ./ FROM base AS prod-deps RUN npm clean-install --omit=dev From 3f4339d2ea12c5c3ac1839f4340041d9c998fee2 Mon Sep 17 00:00:00 2001 From: Felix Hennig Date: Mon, 4 May 2026 16:53:23 +0200 Subject: [PATCH 10/13] feat(website): enable cookieCache and wire up E2E auth cookies Enables better-auth cookieCache (JWE, 1hr) so getSession can validate crafted session cookies without an in-memory store lookup. This makes the E2E auth helper work across the separate test/server processes. Also aligns the E2E test GitHub ID with the seeded collection's userId so ownership checks pass on the edit page. Co-Authored-By: Claude Sonnet 4.6 --- website/src/auth.ts | 7 +++++++ website/tests/collections/collectionForm.spec.ts | 3 ++- website/tests/helpers/auth.ts | 2 ++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/website/src/auth.ts b/website/src/auth.ts index ec3a92b14..d5603d8ac 100644 --- a/website/src/auth.ts +++ b/website/src/auth.ts @@ -5,6 +5,13 @@ import { getGitHubClientId, getGitHubClientSecret } from './config'; export const auth = betterAuth({ // TODO - maybe we can check again if this is read automatically? Should be, according to the docs. secret: process.env.AUTH_SECRET, + session: { + cookieCache: { + enabled: true, + maxAge: 60 * 60, + strategy: 'jwe', + }, + }, user: { additionalFields: { githubId: { diff --git a/website/tests/collections/collectionForm.spec.ts b/website/tests/collections/collectionForm.spec.ts index 39c2c73c9..91f677dc6 100644 --- a/website/tests/collections/collectionForm.spec.ts +++ b/website/tests/collections/collectionForm.spec.ts @@ -1,9 +1,10 @@ import { expect } from '@playwright/test'; +import { E2E_GITHUB_ID } from '../helpers/auth.ts'; import { test } from '../e2e.fixture.ts'; const BACKEND_URL = process.env.BACKEND_URL ?? 'http://localhost:8080'; -const USER_ID = 'e2e-test'; +const USER_ID = E2E_GITHUB_ID; const ORGANISM = 'covid'; const SEED_COLLECTION = { diff --git a/website/tests/helpers/auth.ts b/website/tests/helpers/auth.ts index f8cb67be4..95db5f140 100644 --- a/website/tests/helpers/auth.ts +++ b/website/tests/helpers/auth.ts @@ -5,6 +5,8 @@ import { makeSignature, symmetricEncodeJWT } from 'better-auth/crypto'; // cookieCache must be enabled in auth.ts: // session: { cookieCache: { enabled: true, maxAge: 60 * 60, strategy: 'jwe' } } +export const E2E_GITHUB_ID = '1234567'; + const SECRET = process.env.AUTH_SECRET ?? ''; const COOKIE_PREFIX = 'gen-spectrum'; const DOMAIN = 'localhost'; From 5acbcd96abfff0352d7fe3d0295ff6a9e58062b5 Mon Sep 17 00:00:00 2001 From: Felix Hennig Date: Mon, 4 May 2026 17:00:06 +0200 Subject: [PATCH 11/13] format --- website/tests/collections/collectionForm.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/tests/collections/collectionForm.spec.ts b/website/tests/collections/collectionForm.spec.ts index 91f677dc6..386bdffa0 100644 --- a/website/tests/collections/collectionForm.spec.ts +++ b/website/tests/collections/collectionForm.spec.ts @@ -1,7 +1,7 @@ import { expect } from '@playwright/test'; -import { E2E_GITHUB_ID } from '../helpers/auth.ts'; import { test } from '../e2e.fixture.ts'; +import { E2E_GITHUB_ID } from '../helpers/auth.ts'; const BACKEND_URL = process.env.BACKEND_URL ?? 'http://localhost:8080'; const USER_ID = E2E_GITHUB_ID; From 411947561a715e5781933fb8b262a0fa1c9be898 Mon Sep 17 00:00:00 2001 From: Felix Hennig Date: Mon, 4 May 2026 17:11:36 +0200 Subject: [PATCH 12/13] fix(website): coerce GitHub user ID to string in mapProfileToUser profile.id from GitHub is a number but githubId is declared as string. Without String(), the stored value is a number and ownership checks (currentUserId === collection.ownedBy) fail due to type mismatch. Co-Authored-By: Claude Sonnet 4.6 --- website/src/auth.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/src/auth.ts b/website/src/auth.ts index d5603d8ac..40919d468 100644 --- a/website/src/auth.ts +++ b/website/src/auth.ts @@ -27,7 +27,7 @@ export const auth = betterAuth({ // store the GitHub user ID (i.e. 45882389) in the 'user' object, // which is easy to access from context later on. // the 'id' property cannot be overwritten. - mapProfileToUser: (profile) => ({ githubId: profile.id }), + mapProfileToUser: (profile) => ({ githubId: String(profile.id) }), }, }, advanced: { From 5d71ba2e5edab0cfd66e0e424984384437983ed0 Mon Sep 17 00:00:00 2001 From: Felix Hennig Date: Mon, 4 May 2026 17:24:35 +0200 Subject: [PATCH 13/13] fix(website): suppress eslint warning on String(profile.id) coercion The type says string but GitHub returns a number at runtime. Co-Authored-By: Claude Sonnet 4.6 --- website/src/auth.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/website/src/auth.ts b/website/src/auth.ts index 40919d468..83f1d0a86 100644 --- a/website/src/auth.ts +++ b/website/src/auth.ts @@ -27,6 +27,7 @@ export const auth = betterAuth({ // store the GitHub user ID (i.e. 45882389) in the 'user' object, // which is easy to access from context later on. // the 'id' property cannot be overwritten. + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-conversion -- profile.id is typed as string but GitHub returns a number at runtime; String() ensures it's always stored as a string for consistent ownership checks mapProfileToUser: (profile) => ({ githubId: String(profile.id) }), }, },