From 874749df46cc291ca61f2f98092ccabfdeffbf16 Mon Sep 17 00:00:00 2001 From: yyamashita Date: Sat, 26 Sep 2026 00:19:53 +0900 Subject: Add passkey login and favorite search terms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Single-user auth: any registered passkey logs in as the owner. First registration needs a one-time token that the app writes to data/registration-token when no passkey exists yet; once logged in, more passkeys can be added without a token. Logged in, /favorites manages a list of terms (e.g. band names). Events whose title or artist contains a term (NFKC + case-insensitive) get a ★, and the event list gains an "お気に入りのみ" filter. Co-Authored-By: Claude Opus 5.5 --- .gitignore | 1 + Deploy.md | 16 ++ app/components/EventCard.tsx | 1 + app/components/EventListRow.tsx | 1 + app/components/FilterBar.tsx | 19 ++- app/lib/auth.server.ts | 304 ++++++++++++++++++++++++++++++++++++ app/lib/db.server.ts | 64 +++++++- app/routes.ts | 3 + app/routes/api.auth.$step.ts | 63 ++++++++ app/routes/events.$id.tsx | 1 + app/routes/events._index.tsx | 15 +- app/routes/events.by-date.tsx | 8 +- app/routes/favorites.tsx | 161 +++++++++++++++++++ app/routes/login.tsx | 117 ++++++++++++++ app/routes/venues.tsx | 1 + package-lock.json | 334 +++++++++++++++++++++++++++++++++++++++- package.json | 2 + 17 files changed, 1098 insertions(+), 13 deletions(-) create mode 100644 app/lib/auth.server.ts create mode 100644 app/routes/api.auth.$step.ts create mode 100644 app/routes/favorites.tsx create mode 100644 app/routes/login.tsx diff --git a/.gitignore b/.gitignore index 7c4fad0..0b33ad8 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ # Generated Markdown event files (dev reference, auto-generated) /events/ +registration-token diff --git a/Deploy.md b/Deploy.md index 11921f3..91ae34e 100644 --- a/Deploy.md +++ b/Deploy.md @@ -66,6 +66,22 @@ git remote add hetzner golive:/var/git/tokyo-livehouse-events.git git push hetzner master ``` +## パスキー(ログイン) + +利用者は1人。どのパスキーでログインしても同じオーナー扱い。ログインするとお気に入りワード(`/favorites`)が使える。 + +```bash +# 初回登録: パスキー未登録なら /login へのアクセス時にトークンが自動生成される +curl -s https://golive.yyamashita.com/login > /dev/null +ssh root@localhost 'cat /app/tokyo-livehouse-events/data/registration-token' +# → https://golive.yyamashita.com/login?token=<トークン> を開いて登録(トークンは1回で消える) + +# 全デバイスを失った場合: トークンを手で作って再登録 +ssh root@localhost 'openssl rand -base64 24 | tr "+/" "-_" > /app/tokyo-livehouse-events/data/registration-token' +``` + +2台目以降は、ログイン済みの状態で `/favorites` →「パスキーを追加」。 + ## ローカル開発 ```bash diff --git a/app/components/EventCard.tsx b/app/components/EventCard.tsx index b23742d..ad55a57 100644 --- a/app/components/EventCard.tsx +++ b/app/components/EventCard.tsx @@ -30,6 +30,7 @@ export default function EventCard({ event }: Props) {
{/* Title */}

+ {event.is_favorite ? ★ : null} {event.title}

diff --git a/app/components/EventListRow.tsx b/app/components/EventListRow.tsx index 30800fb..42f3e8b 100644 --- a/app/components/EventListRow.tsx +++ b/app/components/EventListRow.tsx @@ -31,6 +31,7 @@ export default function EventListRow({ event }: Props) { {/* Title + artist */} + {event.is_favorite ? ★ : null} {event.title} {event.artist && ( diff --git a/app/components/FilterBar.tsx b/app/components/FilterBar.tsx index 7a95d2c..f897e67 100644 --- a/app/components/FilterBar.tsx +++ b/app/components/FilterBar.tsx @@ -6,9 +6,11 @@ interface Props { areas?: string[]; defaultDateFrom?: string; defaultDateTo?: string; + /** Show the "favorites only" toggle (logged in only) */ + showFavorites?: boolean; } -export default function FilterBar({ venues, areas, defaultDateFrom, defaultDateTo }: Props) { +export default function FilterBar({ venues, areas, defaultDateFrom, defaultDateTo, showFavorites }: Props) { const [searchParams] = useSearchParams(); return ( @@ -98,6 +100,19 @@ export default function FilterBar({ venues, areas, defaultDateFrom, defaultDateT
+ {showFavorites && ( + + )} + + + {actionData?.error &&

{actionData.error}

} + + {favorites.length === 0 ? ( +

まだ登録されていません

+ ) : ( + + )} + + +
+

パスキー

+
    + {passkeys.map((p) => ( +
  • + {p.label ?? "(名前なし)"} + + {p.last_used_at ? `最終使用 ${p.last_used_at.slice(0, 10)}` : `登録 ${p.created_at.slice(0, 10)}`} + + {passkeys.length > 1 && ( +
    + + + +
    + )} +
  • + ))} +
+
+ + パスキーを追加 + +
+ +
+
+
+ + + ); +} diff --git a/app/routes/login.tsx b/app/routes/login.tsx new file mode 100644 index 0000000..69353ea --- /dev/null +++ b/app/routes/login.tsx @@ -0,0 +1,117 @@ +import { useState } from "react"; +import { Link, useLoaderData, useNavigate } from "react-router"; +import { startAuthentication, startRegistration } from "@simplewebauthn/browser"; +import type { Route } from "./+types/login"; +import { ensureSetupToken, isLoggedIn } from "~/lib/auth.server"; + +export async function loader({ request }: Route.LoaderArgs) { + ensureSetupToken(); + const url = new URL(request.url); + const next = url.searchParams.get("next"); + return { + loggedIn: isLoggedIn(request), + token: url.searchParams.get("token"), + // Only allow same-site relative redirects + next: next && /^\/(?![/\\])/.test(next) ? next : "/favorites", + }; +} + +async function postJson(path: string, body: unknown) { + const res = await fetch(path, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); + const data = await res.json(); + if (!res.ok || data.ok === false) throw new Error(data.error ?? `HTTP ${res.status}`); + return data; +} + +export default function Login() { + const { loggedIn, token, next } = useLoaderData(); + const navigate = useNavigate(); + const [busy, setBusy] = useState(false); + const [error, setError] = useState(null); + const [label, setLabel] = useState(""); + const canRegister = loggedIn || !!token; + + async function run(fn: () => Promise) { + setBusy(true); + setError(null); + try { + await fn(); + navigate(next); + } catch (e) { + const err = e as Error; + setError(err.name === "NotAllowedError" ? "キャンセルされました" : err.message); + } finally { + setBusy(false); + } + } + + const login = () => + run(async () => { + const optionsJSON = await postJson("/api/auth/login-options", {}); + const response = await startAuthentication({ optionsJSON }); + await postJson("/api/auth/login-verify", { response }); + }); + + const register = () => + run(async () => { + const optionsJSON = await postJson("/api/auth/register-options", { token }); + const response = await startRegistration({ optionsJSON }); + await postJson("/api/auth/register-verify", { token, response, label: label || null }); + }); + + return ( +
+
+ + 🎸 ライブに行くしかない + + + {!loggedIn && ( + + )} + + {loggedIn && !token && ( +

+ ログイン済みです。{" "} + + お気に入りへ + +

+ )} + + {canRegister && ( +
+

+ {loggedIn ? "このデバイスのパスキーを追加登録" : "パスキーを新規登録"} +

+ setLabel(e.target.value)} + placeholder="デバイス名(例: iPhone)" + className="rounded-md bg-gray-800 border border-gray-700 px-3 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-indigo-500" + /> + +
+ )} + + {error &&

{error}

} +
+
+ ); +} diff --git a/app/routes/venues.tsx b/app/routes/venues.tsx index 9b8093a..690b1c2 100644 --- a/app/routes/venues.tsx +++ b/app/routes/venues.tsx @@ -26,6 +26,7 @@ export default function Venues() { イベント 日付別 会場一覧 + ★ お気に入り diff --git a/package-lock.json b/package-lock.json index 2ae510b..4ddb459 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,8 @@ "dependencies": { "@react-router/node": "^7.3.0", "@react-router/serve": "^7.3.0", + "@simplewebauthn/browser": "^14.0.0", + "@simplewebauthn/server": "^14.0.2", "better-sqlite3": "^12.9.0", "cheerio": "^1.2.0", "isbot": "^5.1.36", @@ -924,6 +926,12 @@ "node": ">=18" } }, + "node_modules/@hexagon/base64": { + "version": "1.1.28", + "resolved": "https://registry.npmjs.org/@hexagon/base64/-/base64-1.1.28.tgz", + "integrity": "sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==", + "license": "MIT" + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -986,6 +994,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@levischuck/tiny-cbor": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@levischuck/tiny-cbor/-/tiny-cbor-0.2.11.tgz", + "integrity": "sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow==", + "license": "MIT" + }, "node_modules/@mjackson/node-fetch-server": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@mjackson/node-fetch-server/-/node-fetch-server-0.2.0.tgz", @@ -1067,6 +1081,239 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/@peculiar/asn1-android": { + "version": "2.9.5", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-android/-/asn1-android-2.9.5.tgz", + "integrity": "sha512-KnNE4l033gbJ3ZzfYk7fJpSZwhd5xT+dPAnrB8bcwqylHiLN2zjZ0OyFao0LDIOI/ZdXRabV0n3Ww/KvpgZq6w==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.9.5", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-asym-key": { + "version": "2.9.5", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-asym-key/-/asn1-asym-key-2.9.5.tgz", + "integrity": "sha512-U3Q2bUWYNVpfk+oOhPl/+OSHxXaBTQBwMf93jJ7oPi/sbmcY5CnARdjGMwLoXT/ggWSryo5t7A7o3/Gfz0U2WA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-pkcs8": "^2.9.5", + "@peculiar/asn1-schema": "^2.9.5", + "@peculiar/asn1-x509": "^2.9.5", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-cms": { + "version": "2.9.5", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.9.5.tgz", + "integrity": "sha512-4Ivz0fsyFofe0bslNsYcGN9+iETdHX4v+BFwIPr+K/BAt61sHXECP63ff9FntU9zcfVQELJFdUG9so44OjUlBQ==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.9.5", + "@peculiar/asn1-x509": "^2.9.5", + "@peculiar/asn1-x509-attr": "^2.9.5", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-csr": { + "version": "2.9.5", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.9.5.tgz", + "integrity": "sha512-9dO6PcoJeI+1xJ4rG7eINMa6f9Ix/QdRs5UGZ0D7v/R3TzmsbVr5cqAgfYw4xgS9U54ouHYqEz0bIZBbM/UY1g==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.9.5", + "@peculiar/asn1-x509": "^2.9.5", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-ecc": { + "version": "2.9.5", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.9.5.tgz", + "integrity": "sha512-OD+HLlcV7wCL64w0rheX/C0J31DbJce0nf8ulv3UNftQ617JEZE3sQ4cMN6PotXWlJ1M5GQx90G3bywtdKOstw==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.9.5", + "@peculiar/asn1-x509": "^2.9.5", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-pfx": { + "version": "2.9.5", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.9.5.tgz", + "integrity": "sha512-btcH7lbcD4410qnjHvv7F0kEuMNKbX8CFAZSs9CDPWHKzT1byLPHYeR40AnUw9z4CzsijMY0daBFYUvVx75sdA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.9.5", + "@peculiar/asn1-pkcs8": "^2.9.5", + "@peculiar/asn1-rsa": "^2.9.5", + "@peculiar/asn1-schema": "^2.9.5", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-pkcs8": { + "version": "2.9.5", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.9.5.tgz", + "integrity": "sha512-c7UghAGQdBfAHL9JdwcJkAc/lDJKNXfCdYXPXlf8BZdD/fVJjJnXKHxX4gyLhkFTvnGhME7caieSgc2ROtqYTQ==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.9.5", + "@peculiar/asn1-x509": "^2.9.5", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-pkcs9": { + "version": "2.9.5", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.9.5.tgz", + "integrity": "sha512-4R/zMgLSHbQIZRBwP1KzX6iuNHmW3xFWwu9TS9A00N60fmQqp9DkkqnQFyd0mAYMQTrQefuzQ/yP4U8sOLGUgQ==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.9.5", + "@peculiar/asn1-pfx": "^2.9.5", + "@peculiar/asn1-pkcs8": "^2.9.5", + "@peculiar/asn1-schema": "^2.9.5", + "@peculiar/asn1-x509": "^2.9.5", + "@peculiar/asn1-x509-attr": "^2.9.5", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-rsa": { + "version": "2.9.5", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.9.5.tgz", + "integrity": "sha512-kHwBLVMkm9ljlM521ovS7y4z4mr8xuHTdNyyoPlDvckBwFKSgs9HqKBryLBeqHqL2YpIgl+Vspl7kzNGG2phzA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.9.5", + "@peculiar/asn1-x509": "^2.9.5", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.9.5", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.9.5.tgz", + "integrity": "sha512-Ez3wLKVjaxdsLcgeWN4OE31QkM7oBOgKuuBJxldRYAkfYw2C+8zJPcSd/SThnbhszsEOlAmoaS5kIIkN29fGKQ==", + "license": "MIT", + "dependencies": { + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-x509": { + "version": "2.9.5", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.9.5.tgz", + "integrity": "sha512-NP3ecvN6zTDlwCvogIWL7MJN9AZNuskkIbnlhrlkB97Mn2EqirBUQ8GKzsOmdAL3pRvjGu1Uk/a4DNfiZrWxhw==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.9.5", + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-x509-attr": { + "version": "2.9.5", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.9.5.tgz", + "integrity": "sha512-PlbL1a74RZiry7puPCZ5hdk/puvivxYJPbjadr2i7DBjHCJFEKCfTRPKL3DILeZFtzJjn95J5EBtSc4/TB9/6Q==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.9.5", + "@peculiar/asn1-x509": "^2.9.5", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-x509-post-quantum": { + "version": "2.9.5", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-post-quantum/-/asn1-x509-post-quantum-2.9.5.tgz", + "integrity": "sha512-CTznDkboSgwRpXSFhta+Pe5cQ12JtojdMrmOZTWYkBSvbW/1R5pflPIHkpSlaILIfGcAaxIVpXq880xCfJYzow==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-asym-key": "^2.9.5", + "@peculiar/asn1-schema": "^2.9.5", + "@peculiar/asn1-x509": "^2.9.5", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@peculiar/utils/-/utils-2.0.3.tgz", + "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/x509": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-2.1.0.tgz", + "integrity": "sha512-IYbg1R03CSQGWwl24kGyqrdVtixNSbRaDvBg1r5wyYjTP+VwPQkka1BzTgU5+vxiuwqg04OxdvdJ1xYYFIdUSA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.9.4", + "@peculiar/asn1-csr": "^2.9.4", + "@peculiar/asn1-ecc": "^2.9.4", + "@peculiar/asn1-pkcs9": "^2.9.4", + "@peculiar/asn1-rsa": "^2.9.4", + "@peculiar/asn1-schema": "^2.9.4", + "@peculiar/asn1-x509": "^2.9.4", + "@peculiar/asn1-x509-post-quantum": "^2.9.4", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1", + "tsyringe": "^4.10.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -1536,6 +1783,33 @@ "win32" ] }, + "node_modules/@simplewebauthn/browser": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@simplewebauthn/browser/-/browser-14.0.0.tgz", + "integrity": "sha512-1odWVqeEBTl7lJ9zMKLEsmTlnyrDO5iRcTvfMKKk1WThUnp/i8JJdffdj2icP+tty159s4PgwE3BiMoEW9NFow==", + "license": "MIT" + }, + "node_modules/@simplewebauthn/server": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/@simplewebauthn/server/-/server-14.0.2.tgz", + "integrity": "sha512-g+m/xv9/8FRWsDY22CwsTYRzt3WwS9fJrR3P7eY3Ti01FXH5FQ36Xn55v6D+5tb6VajlqOj4hywP/Vxsb6a3Sg==", + "license": "MIT", + "dependencies": { + "@hexagon/base64": "^1.1.27", + "@levischuck/tiny-cbor": "^0.2.2", + "@peculiar/asn1-android": "^2.6.0", + "@peculiar/asn1-ecc": "^2.6.1", + "@peculiar/asn1-rsa": "^2.6.1", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "@peculiar/asn1-x509-post-quantum": "^2.9.4", + "@peculiar/x509": "^2.1.0", + "reflect-metadata": "^0.2.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@tailwindcss/node": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.4.tgz", @@ -1905,6 +2179,20 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/asn1js": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz", + "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==", + "license": "BSD-3-Clause", + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.5", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/babel-dead-code-elimination": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/babel-dead-code-elimination/-/babel-dead-code-elimination-1.0.12.tgz", @@ -4190,6 +4478,24 @@ "once": "^1.3.1" } }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.2.0.tgz", + "integrity": "sha512-BbubeCEyTuQjVMakvJQ/Sxbc93F2pwmbsxONT/ZRrwU7Ua38d8unYTwXpTVLAKJ4BDuH9IGztCjQcd/N/39Dvg==", + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/qs": { "version": "6.14.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", @@ -4329,6 +4635,12 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" + }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -4912,9 +5224,7 @@ "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "optional": true + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, "node_modules/tsx": { "version": "4.21.0", @@ -5420,6 +5730,24 @@ "@esbuild/win32-x64": "0.27.7" } }, + "node_modules/tsyringe": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz", + "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", + "license": "MIT", + "dependencies": { + "tslib": "^1.9.3" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/tsyringe/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", diff --git a/package.json b/package.json index 5232259..ea13320 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,8 @@ "dependencies": { "@react-router/node": "^7.3.0", "@react-router/serve": "^7.3.0", + "@simplewebauthn/browser": "^14.0.0", + "@simplewebauthn/server": "^14.0.2", "better-sqlite3": "^12.9.0", "cheerio": "^1.2.0", "isbot": "^5.1.36", -- cgit v1.3