diff --git a/.claude/commands/audit-deps.md b/.claude/commands/audit-deps.md index dfd5d5e..50bb9ed 100644 --- a/.claude/commands/audit-deps.md +++ b/.claude/commands/audit-deps.md @@ -2,21 +2,43 @@ Review package.json for security vulnerabilities and available updates. +> **No Ministry Platform writes.** This command only touches local dependencies +> (`node_modules`, `package.json`, `package-lock.json`). It never reads or writes +> MP data, so the MP Data Safety rule in CLAUDE.md is not triggered here. + ## Instructions Perform a comprehensive security and update audit of the project's dependencies: +### 0. Sync check FIRST (do this before anything else) + +The installed tree is frequently **stale** relative to `package.json` (someone bumped +a range but didn't reinstall). Auditing a stale tree produces misleading results. + +- Run `npm ls 2>&1 | grep -i invalid` — any `invalid:` markers mean the installed + version doesn't satisfy `package.json`. If found, run `npm install` and re-check. +- To confirm `package.json` and the lockfile are in sync, run `npm ci` (it fails loudly + if they've diverged). If `npm ci` errors, run `npm install` to regenerate the lock, + then investigate the diff before proceeding. +- Only audit **after** the tree matches `package.json`. + ### 1. Vulnerability Analysis - Run `npm audit` to identify known vulnerabilities -- Search the web for recent CVEs affecting major dependencies (Next.js, React, auth libraries, ORMs) -- Check Snyk and NVD databases for any critical issues -- Classify vulnerabilities by severity (Critical, High, Moderate, Low) +- Search the web for recent CVEs affecting the key dependencies listed below +- For each finding, assess **actual exposure**, not just the advisory range: + - Is the vulnerable code path even used? (e.g. a Better Auth `oidc-provider`/`mcp` + advisory does **not** apply here — this app only uses the `genericOAuth` client.) + - Is it dev/build-time only (vitest, vite, esbuild, tsx) vs. runtime/shipped? + - Is it Windows-only? (several esbuild/vite advisories are.) +- Classify by severity (Critical, High, Moderate, Low) **and** by real-world risk. ### 2. Update Analysis - Run `npm outdated` to identify available updates - Categorize updates as: - - **Safe updates**: Patch and minor versions that can be applied immediately - - **Major updates**: Require evaluation of breaking changes and migration effort + - **Safe updates**: Patch and minor versions within existing `^` ranges — usually + applied automatically by `npm install` / `npm audit fix`. + - **Major updates**: Require evaluation of breaking changes, peer-dependency support, + and migration effort. Do **not** apply blindly (see §5 gotchas). ### 3. Use Context7 - Query Context7 for migration guides and breaking changes for any major version updates @@ -28,6 +50,7 @@ Provide a structured report with: #### Security Issues (by severity) - Critical/High: Immediate action required with specific fix commands - Moderate/Low: Assessment of risk and recommended timeline +- **Call out false positives explicitly** (see §5) so they aren't "fixed" later. #### Recommended Updates | Package | Current | Latest | Risk Level | Notes | @@ -38,17 +61,50 @@ Provide a structured report with: 2. **Soon**: Safe updates to apply 3. **Plan for**: Major version upgrades requiring testing -### 5. Optional Execution -If the user requests, execute the recommended action plan: -- Apply safe updates -- Run `npm run build` to verify no breaking changes -- Run `npm run lint` to check for issues -- Report final status - -## Key Dependencies to Always Check -- next / next-auth (framework security) -- react / react-dom (core framework) -- jsonwebtoken / bcryptjs (auth/crypto) -- drizzle-orm / drizzle-kit (database) -- Any AWS SDK packages -- Any packages with known historical vulnerabilities +### 5. Execution — safe procedure & known gotchas + +If the user requests execution, follow this exact order: + +1. `npm install` — syncs the tree to `package.json` ranges (fixes most in-range vulns). +2. `npm audit fix` — applies remaining in-range security patches. + - **NEVER run `npm audit fix --force`.** In this repo it tries to "fix" a + `postcss` advisory by downgrading **`next` to 9.3.3** — a catastrophic breaking + downgrade. `--force` is banned for this command. +3. Verify (all three, in order): + - `npm run build` (Turbopack build + TypeScript check) + - `npm run lint` + - `npm run test:run` (Vitest single run) +4. Report final `npm audit` count and the verification results. + +**Known false positive — do not try to eliminate it:** +- `npm audit` reports 2 moderate `postcss` findings under `node_modules/next/...`. + This is the `postcss` copy **bundled inside Next.js**, not the app's direct dep + (which is already patched). Next controls it; the only offered fix is the `--force` + downgrade above. Leave these 2 as accepted/known. + +**Major version bumps — verify toolchain compatibility, revert cleanly on failure:** +- Bump the version in `package.json`, then `npm install`. Watch for `ERESOLVE` peer + warnings and `invalid:` markers in `npm ls ` — these mean surrounding tooling + hasn't adopted the new major. +- Always run `npm run build` after a major bump. If it fails, **revert**: + restore `package.json`, then `git checkout package-lock.json`, then `npm install`, + and confirm `git status` is clean and the build passes again. +- **Currently blocked (verified, do not retry without upstream support):** + - **TypeScript 7** — breaks the Next.js 16 build worker and is rejected by + `typescript-eslint` (peer capped `<6.1.0`). Stay on TypeScript 6. + - **ESLint 10**, **@types/node 26** — outside current ranges; evaluate individually. + +## Key Dependencies to Always Check (this repo's actual stack) +- **next** — framework; security-critical, runtime. (No `next-auth` — see Better Auth.) +- **react / react-dom** — core framework, runtime. +- **better-auth** — authentication (OAuth client via `genericOAuth`). Auth/crypto-critical. + Check advisories, but confirm whether they apply to the `oidc-provider`/`mcp` plugins + (not used here) vs. the client path (used here). +- **zod** — validation at API boundaries. +- **openai** — API client (used in tooling/scripts). +- **Radix UI / lucide-react / tailwindcss** — UI; low security risk, watch for breaking majors. +- **vitest / vite / esbuild / tsx / jsdom** — dev/build tooling; most advisories here are + dev-only and often Windows-only. Weigh accordingly. + +> Note: this project has **no** `next-auth`, `jsonwebtoken`, `bcryptjs`, Drizzle, or AWS +> SDK. Don't chase those. diff --git a/.claude/playbooks/upgrade-betterauth-1.6-session-integrity.md b/.claude/playbooks/upgrade-betterauth-1.6-session-integrity.md new file mode 100644 index 0000000..55b9199 --- /dev/null +++ b/.claude/playbooks/upgrade-betterauth-1.6-session-integrity.md @@ -0,0 +1,571 @@ +# Playbook: Upgrade Better Auth to 1.6+ Without Breaking MP Session Identity + +You are Claude Code running in a repo that was built on **MPNext** (or forked from +it) at an **older Better Auth version** — 1.4.x or earlier — and now needs to +catch up to the current setup (Better Auth **1.6.x+**). This playbook carries +across the one breaking change that silently destroys the app's connection to +Ministry Platform, plus the guardrails and docs that were added so it can't +regress again. + +If you already run Better Auth ≥1.6 and your avatar/user-menu work, you may only +need Phases 4–6 (the recovery guard + docs) — but **read Phase 1 first** to +confirm. + +**Outcome you're driving toward** + +1. Better Auth is on 1.6.x+ **and** the `userGuid` user `additionalField` is + declared `input: true` — so the MP User_GUID still lands on the session after + the upgrade. +2. That field config is extracted to an exported `userAdditionalFields` const so + a test can assert against the real thing. +3. A regression guard test runs the **real** Better Auth field-parsing function + against the **real** field config, failing if the flag is flipped back OR if a + future Better Auth version changes provider-profile handling again. +4. `AuthWrapper` treats a session with no `userGuid` as unusable and routes it to + a `/session-error` recovery page that can always sign the user out — instead + of trapping them in a dead app with no logout control. +5. `AuthWrapper`'s three redirect branches are unit-tested. +6. `.claude/references/auth.md` documents the `input: true` requirement, a Better + Auth **upgrade checklist**, the broken-session recovery flow, and the + in-memory-adapter risk. + +**Do not skip the discovery phase.** The exact Better Auth version, session +shape, and file layout in a derived repo may differ. The fix is portable; the +wiring is not. And **the only thing that actually proves this fix works is a real +OAuth login** — build/lint/unit tests all pass whether or not `userGuid` reaches +the session. + +## Are you affected? (recognize the symptom) + +After a Better Auth bump from 1.4.x → 1.6.x, users report one or more of: + +- The header **avatar shows a generic placeholder** (never the MP profile photo). +- The **user menu does nothing when clicked** — no dropdown, and critically **no + way to sign out**. +- The app otherwise seems logged in (no redirect to `/signin`). + +Quick confirmation — sign in, then open `/api/auth/get-session` in the browser +and look at the `user` object: + +```jsonc +{ + "user": { + "name": "…", "email": "…", "firstName": "…", "lastName": "…", + "userId": null, // ← symptom: couldn't resolve MP User_ID… + "id": "vKYoq…" // ← Better Auth internal id (fine) + // "userGuid": MISSING // ← ROOT CAUSE: MP User_GUID never persisted + } +} +``` + +If `user.userGuid` is **absent** (and `userId` is therefore `null`), you have this +bug. Proceed. + +## Background — the bug class you're preventing + +MPNext authenticates against Ministry Platform via Better Auth's `genericOAuth` +plugin. Better Auth generates its own internal `user.id`; the MP `User_GUID` (the +OAuth `sub`) is carried on the session as a **custom user `additionalField`** +named `userGuid`, populated server-side from the OAuth profile via +`mapProfileToUser`. **Everything MP-related keys off `userGuid`** — the client +`UserProvider` calls `getCurrentUserProfile(userGuid)` to load the profile +(avatar, name), and `customSession` resolves `User_ID` from it. No `userGuid` → +no profile → dead avatar/menu → `userId: null`. + +**The breaking change:** As of Better Auth **1.6**, the function that pulls +additional fields off an OAuth provider profile, +`parseAdditionalUserInputFromProviderProfile` +(`node_modules/better-auth/dist/db/schema.mjs`), **skips any field declared with +`input: false`** before the user record is created: + +```js +function parseAdditionalUserInputFromProviderProfile(options, profile = {}, action) { + const schema = getFields(options, "user", "input"); + const allowedProfileFields = Object.create(null); + for (const key of Object.keys(profile)) { + if (schema[key]?.input === false) continue; // ← userGuid dropped here + allowedProfileFields[key] = profile[key]; + } + return parseInputData(allowedProfileFields, { fields: schema, action }); +} +``` + +The older MPNext config declared `userGuid` with `input: false` — intending "a +user can't set this during signup." That was correct on 1.4.x, where the field +still flowed through from the OAuth profile. On 1.6 the same flag now **also** +blocks the server-side `mapProfileToUser` mapping, so `userGuid` is silently +dropped and never written to the user record. Nothing throws. Build, lint, and +unit tests all pass. Only a real login reveals it. + +This slipped into MPNext through a routine `npm audit fix` that bumped +`better-auth 1.4.18 → 1.6.23` (resolving GHSA-86j7-9j95-vpqj, a stored-XSS +advisory in the oidc-provider/mcp plugins — not directly exploitable here since +the app uses `genericOAuth` only). The lesson baked into this playbook: **auth +libraries can ship behavior changes across minor versions, and this repo's CI +cannot catch OAuth-runtime regressions.** + +The fix has two independent parts, and you want both: + +1. **Restore the field** — flip `userGuid` to `input: true` (it is populated + server-side, never by user input, so this is safe here) and guard it with a + test that exercises the real library function. +2. **Never trap the user again** — even if some future change breaks `userGuid` + resolution, a session without it must still offer a way to sign out. Add a + server-side guard + recovery page. + +## Phase 1 — Discovery + +Before changing anything, answer these by reading the repo: + +1. **What Better Auth version is installed vs. declared?** + ``` + node -p "require('./node_modules/better-auth/package.json').version" + node -p "require('./package.json').dependencies['better-auth']" + ``` + - If installed is `< 1.6.0`, the upgrade hasn't happened yet — you're doing a + proactive fix (good). If it's `≥ 1.6.0`, the bug may already be live. + +2. **Where is the Better Auth server config, and how is `userGuid` declared?** + - In MPNext: `src/lib/auth.ts`. Grep in a derived repo: + `grep -rn "additionalFields\|betterAuth\|mapProfileToUser" src/`. + - Find the `user.additionalFields.userGuid` block. Note its `input:` value. + - If the field is named something other than `userGuid` (e.g. `mpUserGuid`, + `userGUID`), use that name consistently throughout. + +3. **How is `userGuid` populated?** Confirm a `genericOAuth` config with + `mapProfileToUser` returning `{ userGuid: profile.id }` (or equivalent), and a + `getUserInfo` that returns `id: profile.sub`. If the derived repo uses a + different provider plugin or maps the sub differently, adapt — but the + `input:` fix applies to any additionalField populated from an OAuth profile. + +4. **Is `customSession` present, and does it read `userGuid`?** + - In MPNext, `customSession` splits `user.name` into `firstName`/`lastName` + and resolves `userId` (MP `User_ID`) from `userGuid` via a cached `dp_Users` + lookup. If `userGuid` is missing, `userId` comes back `null` — a useful + secondary signal. + +5. **Where is the auth guard / layout wrapper?** + - In MPNext: `src/components/layout/auth-wrapper.tsx`, used by + `src/app/(web)/layout.tsx`. `/signin` lives **outside** the `(web)` route + group so it isn't self-guarded. + - Grep: `grep -rn "getSession\|AuthWrapper\|redirect(\"/signin\")" src/app src/components`. + - Identify the route-group structure so the new `/session-error` page can live + **outside** the guarded group (or it will redirect-loop). + +6. **Where is the sign-out action?** + - In MPNext: `handleSignOut` in `src/components/user-menu/actions.ts` (clears + the Better Auth session, then redirects to MP's OIDC endsession endpoint). + The recovery page reuses it. + +7. **Testing framework + path alias.** MPNext uses Vitest with `vi.hoisted()` + and `@/*` → `src/*`. Confirm in `vitest.config.ts` / `tsconfig.json`. + +8. **Is there a database adapter?** + ``` + grep -rn "database" src/lib/auth.ts + node -e "const d={...require('./package.json').dependencies,...require('./package.json').devDependencies};console.log(Object.keys(d).filter(k=>/prisma|drizzle|kysely|mongodb|better-sqlite|pg|mysql2|adapter/i.test(k)).join('\n')||'(none)')" + ``` + - No `database` key + no adapter package = **in-memory adapter**. Note this; + it's a related fragility documented in Phase 6 (sessions die on every server + restart / serverless cold start). + +Write the answers down. Only proceed when each has an answer or is raised with the +user. + +## Phase 2 — Bump Better Auth and flip `userGuid` to `input: true` + +**1.** If not already on 1.6+, upgrade within your policy (e.g. `npm install +better-auth@^1.6` or via `npm audit fix`). Re-run `npm install` so the lockfile +syncs. + +**2.** In the auth server config, extract the additional-fields object to an +exported const and set `input: true`. In MPNext this is `src/lib/auth.ts`: + +```ts +/** + * Custom fields added to the Better Auth `user` record. + * + * `userGuid` (the MP User_GUID / OAuth `sub`) MUST keep `input: true`. It is + * populated server-side from the OAuth profile via `mapProfileToUser`. As of + * better-auth 1.6, `parseAdditionalUserInputFromProviderProfile` strips any + * additional field declared with `input: false` BEFORE the user record is + * created — so `input: false` silently drops `userGuid`, which breaks every MP + * profile lookup (avatar, user menu, User_ID resolution). There is no + * user-facing form that sets this field, so allowing input carries no practical + * risk here. `src/auth.test.ts` guards this against future regressions. + */ +export const userAdditionalFields = { + userGuid: { + type: "string" as const, + required: false, + input: true, + }, +}; +``` + +**3.** Reference it in the config: + +```ts +const options = { + // … + user: { + additionalFields: userAdditionalFields, + }, + // … +} satisfies BetterAuthOptions; +``` + +**Why `input: true` is safe here:** the app uses `genericOAuth` only — there is +no email/password signup form and no exposed update-user endpoint that accepts +`userGuid`. The field is only ever set server-side from the verified OAuth +profile. If your derived repo *does* expose user-writable endpoints, prefer +populating the field via a `databaseHooks.user.create.before` hook instead, so +you can keep `input: false`. Otherwise `input: true` is the minimal, correct fix. + +## Phase 3 — Add the field-persistence regression guard + +The `input:` filter lives in Better Auth's own code, so the highest-value guard +runs the **real** library function against your **real** field config. It's +importable via the `better-auth/db` subpath. + +Add this to the auth config test (MPNext: `src/auth.test.ts`): + +```ts +import { parseAdditionalUserInputFromProviderProfile } from 'better-auth/db'; +import { userAdditionalFields } from '@/lib/auth'; + +// …inside a describe block… + +/** + * Regression guard for the better-auth 1.6 upgrade incident. + * + * 1.6 changed parseAdditionalUserInputFromProviderProfile to strip any user + * additionalField declared with `input: false` before the user record is + * created. `userGuid` is populated server-side via mapProfileToUser, so + * `input: false` silently dropped it — leaving session.user.userGuid undefined + * and breaking every MP profile lookup. + * + * Runs the REAL better-auth parse function against our REAL field config, so it + * fails if (a) someone flips userGuid back to input:false, or (b) a future + * better-auth upgrade changes how provider-profile fields are parsed. + */ +it('persists userGuid from the OAuth provider profile (better-auth 1.6 guard)', () => { + const guid = 'ab12cd34-ef56-7890-abcd-ef1234567890'; + const options = { user: { additionalFields: userAdditionalFields } }; + + const parsed = parseAdditionalUserInputFromProviderProfile( + options, + { userGuid: guid }, + 'create', + ); + + expect(parsed).toHaveProperty('userGuid', guid); +}); +``` + +Prove it's a real tripwire (not a tautology) — with `input: false` the function +returns `{}`; with `input: true` it returns `{ userGuid }`: + +``` +node --input-type=module -e " +import { parseAdditionalUserInputFromProviderProfile } from 'better-auth/db'; +const bad = { user:{ additionalFields:{ userGuid:{ type:'string', required:false, input:false } } } }; +console.log(JSON.stringify(parseAdditionalUserInputFromProviderProfile(bad,{userGuid:'x'},'create'))); +" +``` + +Run: `npm run test:run src/auth.test.ts` — must pass. + +## Phase 4 — Add the broken-session recovery guard + +The trap that made this so painful: with no `userGuid`, the header renders a +non-interactive fallback, so the user had **no sign-out control** and was stuck. +Add a defensive net so an unusable session always has an exit — independent of +the root cause. + +**1.** In the auth guard (`src/components/layout/auth-wrapper.tsx`), add a +`userGuid` check after the existing no-session redirect: + +```tsx +import { auth } from "@/lib/auth"; +import { headers } from "next/headers"; +import { redirect } from "next/navigation"; + +export async function AuthWrapper({ children }: { children: React.ReactNode }) { + const session = await auth.api.getSession({ headers: await headers() }); + + if (!session) { + redirect("/signin"); + } + + // A session without a userGuid is unusable: every MP lookup keys off userGuid, + // and without it the header avatar/menu never renders — which leaves the user + // with no way to even sign out (the trap behind the better-auth 1.6 regression). + // Route these broken sessions to a recovery page that CAN sign them out, + // rather than rendering a dead app. `/session-error` lives outside the (web) + // route group, so it is not wrapped by AuthWrapper and cannot redirect-loop. + const userGuid = (session.user as { userGuid?: string | null }).userGuid; + if (!userGuid) { + redirect("/session-error"); + } + + return <>{children}; +} +``` + +**Why here, not the proxy or cookie deletion?** +- The proxy (`src/proxy.ts`) uses a fast cookie-*existence* check and doesn't + decode the JWT, so it can't cheaply see `userGuid`. +- You can't delete the cookie during a Server Component render — Next.js only + allows cookie mutation in actions/route handlers. That's exactly why recovery + uses a server-action form button (below), not an auto-clear. +- `AuthWrapper` already calls `auth.api.getSession()`, so inspecting `userGuid` + there is authoritative and free. + +**2.** Create the recovery page **outside** the guarded route group so it can't +loop. In MPNext, `/session-error` sits beside `/signin`, outside `(web)`: +`src/app/session-error/page.tsx`: + +```tsx +import { handleSignOut } from "@/components/user-menu/actions"; + +/** + * Recovery page for authenticated-but-unusable sessions. + * + * AuthWrapper redirects here when a session exists but has no `userGuid`. Such a + * session can't load an MP profile, so the header avatar/menu — and therefore + * the normal sign-out control — never render. This page gives the user an + * unconditional way out via `handleSignOut`. It lives outside the (web) route + * group, so it is NOT wrapped by AuthWrapper and cannot cause a redirect loop. + */ +export default function SessionErrorPage() { + return ( +
+
+

We couldn't load your account

+

+ Your sign-in completed, but it didn't include the Ministry Platform + user link we need to load your profile. Please sign out and sign in + again. If this keeps happening, contact your administrator. +

+
+ +
+
+
+ ); +} +``` + +**3.** Confirm the proxy allows the page. In MPNext, the proxy allows any path +when a (broken but present) session cookie exists, so `/session-error` is +reachable in the failure case; with no cookie it redirects to `/signin`, which is +fine. If your derived repo's proxy is stricter, add `/session-error` to its +public paths. + +## Phase 5 — Add the AuthWrapper guard tests + +Create `src/components/layout/auth-wrapper.test.tsx`. Mock `redirect` so it throws +(mirroring `next/navigation`, which halts execution), then assert each branch: + +```tsx +import { describe, it, expect, vi, beforeEach } from "vitest"; + +const { mockGetSession, mockRedirect } = vi.hoisted(() => ({ + mockGetSession: vi.fn(), + mockRedirect: vi.fn((url: string) => { + throw new Error(`REDIRECT:${url}`); + }), +})); + +vi.mock("@/lib/auth", () => ({ auth: { api: { getSession: mockGetSession } } })); +vi.mock("next/headers", () => ({ headers: vi.fn(async () => new Headers()) })); +vi.mock("next/navigation", () => ({ redirect: mockRedirect })); + +import { AuthWrapper } from "./auth-wrapper"; + +describe("AuthWrapper", () => { + beforeEach(() => vi.clearAllMocks()); + + it("redirects to /signin when there is no session", async () => { + mockGetSession.mockResolvedValue(null); + await expect(AuthWrapper({ children: null })).rejects.toThrow("REDIRECT:/signin"); + }); + + it("redirects to /session-error when the session has no userGuid", async () => { + mockGetSession.mockResolvedValue({ user: { id: "ba-id", name: "No Guid" } }); + await expect(AuthWrapper({ children: null })).rejects.toThrow("REDIRECT:/session-error"); + }); + + it("redirects to /session-error when userGuid is null", async () => { + mockGetSession.mockResolvedValue({ user: { id: "ba-id", userGuid: null } }); + await expect(AuthWrapper({ children: null })).rejects.toThrow("REDIRECT:/session-error"); + }); + + it("renders children when the session has a userGuid", async () => { + mockGetSession.mockResolvedValue({ user: { id: "ba-id", userGuid: "guid-1" } }); + const result = await AuthWrapper({ children: "CONTENT" }); + expect(mockRedirect).not.toHaveBeenCalled(); + expect(result).toBeTruthy(); + }); +}); +``` + +## Phase 6 — Update the auth reference doc + +Bring `.claude/references/auth.md` current. Four edits: + +1. **Correct the `additionalFields` snippet** — an older doc shows `input: false`, + which would lead the next person straight back into the bug. Change it to + `input: true` and add a warning: + + > ⚠️ **`userGuid` MUST keep `input: true`.** It is populated server-side from + > the OAuth profile via `mapProfileToUser`, not by user input. As of + > better-auth 1.6, `parseAdditionalUserInputFromProviderProfile` strips any + > additional field declared with `input: false` before creating the user + > record → `session.user.userGuid` becomes `undefined` → blank avatar, dead + > user menu, `userId: null`. Guarded by `src/auth.test.ts`. + +2. **Add a "Better Auth Upgrade Checklist"** — the process gap that let this ship: + + > `npm audit fix` / `npm update` can bump better-auth across minor versions. + > CI (build + lint + unit tests) does not exercise a real OAuth login, so + > session/OAuth regressions ship silently. After any better-auth version + > change: + > 1. Read the changelog for `genericOAuth`, `customSession`, + > `additionalFields`, cookie-cache/session serialization, `mapProfileToUser`. + > 2. `npm run test:run src/auth.test.ts` (the 1.6 guard). + > 3. **Manual smoke test (required — nothing else catches this):** `npm run + > dev`, sign in, open `/api/auth/get-session`, confirm `user.userGuid` and + > `user.userId` are non-null, confirm the avatar/menu render. Sign out and + > log in **fresh** — don't test against a stale session. + > 4. If `userGuid` is missing, inspect + > `parseAdditionalUserInputFromProviderProfile` in + > `node_modules/better-auth/dist/db/schema.mjs`. + +3. **Document the recovery flow** — `AuthWrapper` redirects sessions with no + `userGuid` to `/session-error` (outside the guarded route group), which offers + an unconditional sign-out. Guarded by `auth-wrapper.test.tsx`. + +4. **Elevate the in-memory-adapter risk** (if Phase 1 found no `database`): with + no adapter, sessions live only in-memory + cookies and are lost on every + process restart. On serverless/Vercel, **every cold start has an empty session + store**, so once the 1-hour cookie cache expires a request on a fresh instance + returns `null` and the user intermittently appears logged out. Recommend + configuring a persistent adapter before production. + +## Phase 7 — Verify + +1. `npm run lint` — clean. +2. `npm run test:run` — all pass, including the new `auth.test.ts` guard and + `auth-wrapper.test.tsx`. +3. `npm run build` — succeeds and emits the `/session-error` route. (`npx tsc + --noEmit` may surface pre-existing errors in unrelated test files; the build + excludes test files. Note any in the PR, don't fix in this change.) +4. **Manual login smoke test — MANDATORY. This is the only step that actually + proves the fix.** Steps 1–3 all pass whether or not `userGuid` reaches the + session. + - `npm run dev`, then **sign out and log in fresh** (existing sessions predate + the corrected user-record shape). + - Open `/api/auth/get-session`: confirm `user.userGuid` is present and + `user.userId` is non-null. + - Confirm the header avatar renders and the user menu opens with a working + sign-out. +5. **Optionally prove the recovery path:** temporarily force `userGuid` undefined + (e.g. comment out the `mapProfileToUser` return in a throwaway branch), log in, + and confirm you land on `/session-error` with a working sign-out button. + Revert. + +## Phase 8 — Branch, commit, PR + +Use the repo's conventions. MPNext split this into two commits — adapt as needed: + +``` +fix(auth): keep userGuid additionalField as input:true (better-auth 1.6 regression) + +better-auth 1.6's parseAdditionalUserInputFromProviderProfile strips any user +additionalField declared with input:false before creating the user record. Our +userGuid field (MP User_GUID / OAuth sub) is populated server-side via +mapProfileToUser, so input:false silently dropped it: session.user.userGuid +became undefined, breaking every MP profile lookup — blank avatar, dead user +menu, userId:null. + +- Flip userGuid to input:true; extract to exported userAdditionalFields const. +- Add a regression guard test running the real better-auth parse function + (better-auth/db) against the real field config. +- Correct auth.md, add a Better Auth upgrade checklist and DB-adapter warning. +``` + +``` +feat(auth): recover from sessions missing userGuid instead of trapping the user + +A session without a userGuid rendered a dead header — no avatar/menu, no +sign-out control, user stuck. Add a defensive guard so an unusable session +always has an exit, independent of root cause. + +- AuthWrapper: session with no userGuid → redirect to /session-error. +- New /session-error page: recovery screen with a handleSignOut form button, + outside the (web) route group so it can't redirect-loop. +- auth-wrapper.test.tsx covers all three guard branches. +``` + +Open the PR, request review, don't self-merge unless that's normal here. + +## What "done" looks like + +- [ ] Better Auth is on 1.6.x+ and `userGuid` is declared `input: true`, extracted + to an exported `userAdditionalFields` const. +- [ ] `src/auth.test.ts` has a guard that runs `parseAdditionalUserInputFromProviderProfile` + (from `better-auth/db`) against the real config and asserts `userGuid` + survives. +- [ ] `AuthWrapper` redirects sessions with no `userGuid` to `/session-error`. +- [ ] `/session-error` exists **outside** the guarded route group and offers a + working sign-out via `handleSignOut`. +- [ ] `auth-wrapper.test.tsx` covers no-session → `/signin`, no-userGuid → + `/session-error`, and healthy → renders children. +- [ ] `.claude/references/auth.md` has the `input: true` warning, upgrade + checklist, recovery-flow note, and (if applicable) the in-memory-adapter + warning. +- [ ] Lint, full test suite, and build pass; **and a real fresh login shows + `userGuid` + `userId` populated and a working avatar/menu.** + +## Appendix — Better Auth ↔ MP identity facts (mental-model upgrade) + +Key facts someone who built on an older MPNext should internalize: + +- **`user.id` ≠ MP `User_GUID`.** Better Auth generates its own internal + nanoid-style `user.id`. The OAuth `sub` becomes the `accountId` in the account + table, **not** `user.id`. The MP `User_GUID` is carried separately as the + `userGuid` additionalField. Use `userGuid` for all MP lookups; use `user.id` + only for "is there a session" guards. +- **Where each session field comes from:** + | Field | Source | + | --- | --- | + | `user.id` | Better Auth internal (generated) | + | `user.userGuid` | `mapProfileToUser` ← OAuth `sub` — **the field this playbook protects** | + | `user.name` | `getUserInfo`: `` `${given_name} ${family_name}` `` | + | `user.image` | explicitly `undefined` — avatar comes from MP `Image_GUID`, not the session | + | `user.firstName` / `lastName` | `customSession` splits `user.name` | + | `user.userId` | `customSession` resolves MP `User_ID` from `userGuid` (cached `dp_Users` lookup); `null` if `userGuid` missing | +- **`customSession` runs on every `getSession()`** (including cookie-cache hits, + because the plugin wraps the `/get-session` endpoint). Keep it cheap — name + splitting + a cached `User_ID` lookup; no per-request MP profile fetch. +- **Client type inference:** `customSessionClient()` in + `auth-client.ts` makes the server-augmented fields visible on + `authClient.useSession()`. `userGuid` still needs a cast in most call sites + because `genericOAuth`'s `additionalFields` aren't inferred — e.g. + `(session?.user as { userGuid?: string })?.userGuid`. +- **The avatar/menu chain end-to-end:** `useSession()` → `session.user.userGuid` + → `UserProvider` → `getCurrentUserProfile(userGuid)` → `MPUserProfile` + (`Image_GUID`, names) → `Header` renders the photo + `UserMenu`. Any break in + `userGuid` collapses the whole chain to the non-interactive fallback. There are + **no text initials** in this UI — the fallback is a generic `UserCircleIcon`, + so a plain circle with no photo is expected when a contact has no `Image_GUID`. +- **No database by default:** Better Auth uses an in-memory adapter when no + `database` is configured. Sessions don't survive restarts/cold starts. This is + the top follow-up refactor, independent of this playbook. diff --git a/.claude/references/auth.md b/.claude/references/auth.md index b466686..fb25f43 100644 --- a/.claude/references/auth.md +++ b/.claude/references/auth.md @@ -43,7 +43,8 @@ The cast is needed because `customSessionClient` type inference doesn't include | `src/proxy.ts` | Route protection (session cookie check) | | `src/contexts/user-context.tsx` | `UserProvider` — loads MP user profile client-side | | `src/contexts/session-context.tsx` | `useAppSession()` — thin wrapper around `authClient.useSession()` | -| `src/components/layout/auth-wrapper.tsx` | Server component — redirects unauthenticated users | +| `src/components/layout/auth-wrapper.tsx` | Server guard for the (web) group — redirects to `/signin` (no session) or `/session-error` (session without `userGuid`) | +| `src/app/session-error/page.tsx` | Recovery page for broken sessions — provides a sign-out even when the header/menu can't render (outside the (web) group, so not self-guarded) | | `src/components/user-menu/actions.ts` | `handleSignOut()` — OIDC logout flow | | `src/app/signin/page.tsx` | Sign-in page — auto-redirects to OAuth | @@ -78,17 +79,28 @@ The cast is needed because `customSessionClient` type inference doesn't include ### User Additional Fields ```typescript +// Exported as `userAdditionalFields` from src/lib/auth.ts user: { additionalFields: { userGuid: { type: "string", required: false, - input: false, // Cannot be set by users during signup + input: true, // MUST be true — see warning below }, }, } ``` +> ⚠️ **`userGuid` MUST keep `input: true`.** It is populated server-side from the +> OAuth profile via `mapProfileToUser`, **not** by user input. As of better-auth +> **1.6**, `parseAdditionalUserInputFromProviderProfile` strips any additional +> field declared with `input: false` *before creating the user record*. Setting +> `input: false` therefore silently drops `userGuid` → `session.user.userGuid` +> becomes `undefined` → every MP profile lookup fails (blank avatar, dead user +> menu, `userId: null`). This regressed once during the 1.4→1.6 upgrade. +> `src/auth.test.ts` guards it by running the real better-auth parse function +> against the real field config. Do not "tighten" this back to `input: false`. + ### customSession Callback The `customSession` callback only does lightweight name splitting. It does **not** make any API calls. Profile loading is handled by `UserProvider` on the client side. @@ -175,6 +187,24 @@ Uses `getSessionCookie()` from `better-auth/cookies` for fast cookie-only checks Everything else requires a valid session cookie. Missing cookie → redirect to `/signin`. +## Broken-Session Recovery + +A session can authenticate successfully yet lack a `userGuid` (e.g. the +better-auth 1.6 regression, or a future provider/config change). Without a +`userGuid` the MP profile never loads, so `Header` renders its non-interactive +fallback — no dropdown, and therefore **no way to sign out**. To prevent that +dead end: + +- `AuthWrapper` treats a session with no `userGuid` as unusable and redirects to + `/session-error`. +- `/session-error` (in `src/app/session-error/`, **outside** the `(web)` route + group so it isn't wrapped by `AuthWrapper`) renders a plain page with a + `handleSignOut` form button, giving the user an unconditional exit. +- After sign-out the Better Auth cookie is cleared and the user is bounced to + MP's endsession endpoint, then back through `/signin` for a fresh login. + +Guarded by `src/components/layout/auth-wrapper.test.tsx`. + ## Session Access Patterns ### Server Components @@ -254,9 +284,33 @@ The following URLs must be configured in the Ministry Platform OAuth client: - **OAuth2 Callback URL**: `{APP_URL}/api/auth/oauth2/callback/ministry-platform` - **Post-Logout Redirect URI**: `{APP_URL}` (or `{APP_URL}/signin`) +## Better Auth Upgrade Checklist + +`npm audit fix` and `npm update` can bump `better-auth` across **minor** versions +(e.g. 1.4 → 1.6). Our CI (`build` + `lint` + unit tests) does **not** exercise a +real OAuth login, so session/OAuth regressions ship silently. After any change to +the `better-auth` version, do this before merging: + +1. **Read the changelog** between the old and new version, focusing on: + `genericOAuth`, `customSession`, `additionalFields`, cookie cache / session + serialization, and `mapProfileToUser`. +2. **Run the auth tests**: `npm run test:run src/auth.test.ts` — the + `better-auth 1.6 guard` test verifies `userGuid` still survives provider-profile + parsing against the real library. +3. **Manual smoke test (required — nothing else catches this):** + - `npm run dev`, sign in through Ministry Platform. + - Open `/api/auth/get-session` and confirm the session `user` object contains + **`userGuid`** (non-null) and **`userId`** (non-null). + - Confirm the header avatar renders and the user menu opens. + - Existing sessions predate the new user-record shape, so **sign out and log in + fresh** — don't test against a stale session. +4. If `userGuid` is missing, check `parseAdditionalUserInputFromProviderProfile` + in `node_modules/better-auth/dist/db/schema.mjs` — the library may have changed + how additional fields flow from the OAuth profile into the user record. + ## Known Limitations -1. **No database**: In-memory adapter means all sessions are lost on server restart. Users must re-login after each restart. For production, configure a database adapter. +1. **No database (top refactor priority)**: With no `database` in the config, Better Auth uses an in-memory adapter. Sessions live only in the in-memory store + cookies, so they are lost whenever the process restarts. On serverless/Vercel this is severe: **every cold start or new function instance has an empty session store**, so once the 1-hour JWT cookie cache expires, a request that lands on a fresh instance returns `null` and the user appears logged out (blank avatar / redirect to `/signin`) intermittently. This also makes auth bugs hard to reproduce. **Recommendation:** configure a persistent database adapter (e.g. a Vercel Marketplace Postgres/Neon, or SQLite for local dev) before relying on this in production. 2. **mapProfileToUser type narrowness**: The genericOAuth TypeScript type for `mapProfileToUser` doesn't include `additionalFields`. The return must be cast to `Record` for extra fields. The runtime code does pass them through correctly. 3. **userGuid type cast**: `session.user.userGuid` requires a type cast because `customSessionClient` doesn't infer `additionalFields` from `genericOAuth`. This is a Better Auth type limitation. 4. **Token refresh**: Not explicitly implemented. The `storeAccountCookie` stores refresh tokens, but automatic refresh behavior in stateless mode is unverified. diff --git a/package-lock.json b/package-lock.json index d90ca68..7508553 100644 --- a/package-lock.json +++ b/package-lock.json @@ -133,13 +133,13 @@ "license": "MIT" }, "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -148,9 +148,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", - "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true, "license": "MIT", "engines": { @@ -158,22 +158,22 @@ } }, "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -190,14 +190,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -207,14 +207,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -224,9 +224,9 @@ } }, "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "dev": true, "license": "MIT", "engines": { @@ -234,29 +234,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -266,9 +266,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "dev": true, "license": "MIT", "engines": { @@ -276,9 +276,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, "license": "MIT", "engines": { @@ -286,9 +286,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "dev": true, "license": "MIT", "engines": { @@ -296,27 +296,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", - "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -336,33 +336,33 @@ } }, "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", "debug": "^4.3.1" }, "engines": { @@ -370,14 +370,14 @@ } }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -394,9 +394,9 @@ } }, "node_modules/@better-auth/core": { - "version": "1.6.11", - "resolved": "https://registry.npmjs.org/@better-auth/core/-/core-1.6.11.tgz", - "integrity": "sha512-LrwidLCV8azdMGjvtwp30nj9tIv1BwI3VhtC0UaGSjQkAVWw4bN42I8qwbxRziPeSQoj+zUVkOpxZzAWBDARtQ==", + "version": "1.6.23", + "resolved": "https://registry.npmjs.org/@better-auth/core/-/core-1.6.23.tgz", + "integrity": "sha512-beEhOs0uVeOxYOZKUfIEBd/nQV2Bd4/6wyLxZ0OFkn6CMTK2Vi+hXuZLnyPBeB6RdHpebEoJWiHqwHxBIxgPDQ==", "license": "MIT", "peer": true, "dependencies": { @@ -405,13 +405,13 @@ "zod": "^4.3.6" }, "peerDependencies": { - "@better-auth/utils": "0.4.0", - "@better-fetch/fetch": "1.1.21", + "@better-auth/utils": "0.4.2", + "@better-fetch/fetch": "1.3.1", "@cloudflare/workers-types": ">=4", "@opentelemetry/api": "^1.9.0", - "better-call": "1.3.5", + "better-call": "1.3.7", "jose": "^6.1.0", - "kysely": "^0.28.5", + "kysely": "^0.28.5 || ^0.29.0", "nanostores": "^1.0.1" }, "peerDependenciesMeta": { @@ -424,13 +424,13 @@ } }, "node_modules/@better-auth/drizzle-adapter": { - "version": "1.6.11", - "resolved": "https://registry.npmjs.org/@better-auth/drizzle-adapter/-/drizzle-adapter-1.6.11.tgz", - "integrity": "sha512-4jpkETIGZOHCf7BK4jnu22fdN6jjomH0/HhEzkaWy3+Eppi5PYlHTF/460jrTmA3Xc+Vqwp9t282ymHiEPypGw==", + "version": "1.6.23", + "resolved": "https://registry.npmjs.org/@better-auth/drizzle-adapter/-/drizzle-adapter-1.6.23.tgz", + "integrity": "sha512-2+/PTVfIP9E7iz6af8TB3lhnowHUj9ljC66kECmHaFEdUqPgzHoWux9epotKwO7XDg2ui4ttWQ8CMeNFLvQeKQ==", "license": "MIT", "peerDependencies": { - "@better-auth/core": "^1.6.11", - "@better-auth/utils": "0.4.0", + "@better-auth/core": "^1.6.23", + "@better-auth/utils": "0.4.2", "drizzle-orm": "^0.45.2" }, "peerDependenciesMeta": { @@ -440,14 +440,14 @@ } }, "node_modules/@better-auth/kysely-adapter": { - "version": "1.6.11", - "resolved": "https://registry.npmjs.org/@better-auth/kysely-adapter/-/kysely-adapter-1.6.11.tgz", - "integrity": "sha512-/g8M9RfIjdcZDnbstSUvQiINkvdNlCeZr248zwqx2/PVksQI1MhQofbzUn3RnQnbPKp0EPwpX/dR3oudRFenUg==", + "version": "1.6.23", + "resolved": "https://registry.npmjs.org/@better-auth/kysely-adapter/-/kysely-adapter-1.6.23.tgz", + "integrity": "sha512-zbNJsMbG09exfkGyvFqBLLqWoMPAUWjxCuUnEK5AsjbYoZeIjj/QGZgdf4CapVWryKxjA9Q6Jlr6fbiPpC3VAg==", "license": "MIT", "peerDependencies": { - "@better-auth/core": "^1.6.11", - "@better-auth/utils": "0.4.0", - "kysely": "^0.28.17" + "@better-auth/core": "^1.6.23", + "@better-auth/utils": "0.4.2", + "kysely": "^0.28.17 || ^0.29.0" }, "peerDependenciesMeta": { "kysely": { @@ -456,23 +456,23 @@ } }, "node_modules/@better-auth/memory-adapter": { - "version": "1.6.11", - "resolved": "https://registry.npmjs.org/@better-auth/memory-adapter/-/memory-adapter-1.6.11.tgz", - "integrity": "sha512-hpdfw0BBf8MuzLkIdmbcUZICbY9r/bhLO2RxSnkzT5+/O+0I0u2I8+m0YUP7vNllP/ZCKASHOYgXPLO75Z0f9Q==", + "version": "1.6.23", + "resolved": "https://registry.npmjs.org/@better-auth/memory-adapter/-/memory-adapter-1.6.23.tgz", + "integrity": "sha512-krIiR0pIVkaKlAzm690n5bcMW4NGbqeMg0HQSD9fz/KcQF/eWLqcq9gG/BhHTj2i/y96qH+W5JWPmaSOS5iTgQ==", "license": "MIT", "peerDependencies": { - "@better-auth/core": "^1.6.11", - "@better-auth/utils": "0.4.0" + "@better-auth/core": "^1.6.23", + "@better-auth/utils": "0.4.2" } }, "node_modules/@better-auth/mongo-adapter": { - "version": "1.6.11", - "resolved": "https://registry.npmjs.org/@better-auth/mongo-adapter/-/mongo-adapter-1.6.11.tgz", - "integrity": "sha512-3Tor8rSv8vSEIMEaV2PFpPEuVhqc1gNoZ6eGvoh3LwExXXuj8madew6ob+H1pH7Aphn3Ar5PQ08AguT8TbwFAA==", + "version": "1.6.23", + "resolved": "https://registry.npmjs.org/@better-auth/mongo-adapter/-/mongo-adapter-1.6.23.tgz", + "integrity": "sha512-7+QdevitGlKBbP6JbiSk5SBnzPsKV/mDrQBGBn8hwByQLeJwqpqbuBPw7ZI8vzUlFfAAnyFiqwP3Eb8mxnp7pA==", "license": "MIT", "peerDependencies": { - "@better-auth/core": "^1.6.11", - "@better-auth/utils": "0.4.0", + "@better-auth/core": "^1.6.23", + "@better-auth/utils": "0.4.2", "mongodb": "^6.0.0 || ^7.0.0" }, "peerDependenciesMeta": { @@ -482,13 +482,13 @@ } }, "node_modules/@better-auth/prisma-adapter": { - "version": "1.6.11", - "resolved": "https://registry.npmjs.org/@better-auth/prisma-adapter/-/prisma-adapter-1.6.11.tgz", - "integrity": "sha512-Pw+7q7zTp+VSci1V+CYMvuxIbAeVMZLe4lRo46LJoAKMHfjFl5T/ycsyFvWs/DkWC7n9gZZzRDEbHp0I5FiKKw==", + "version": "1.6.23", + "resolved": "https://registry.npmjs.org/@better-auth/prisma-adapter/-/prisma-adapter-1.6.23.tgz", + "integrity": "sha512-2qSdzidq4tkb1eS5TTqb4Nzg0mdZWm3Qky9SYeXeb8PpVQbC2sxqJhEM5mK7y12uU6I8hc64wO9f7AFVNL+6UQ==", "license": "MIT", "peerDependencies": { - "@better-auth/core": "^1.6.11", - "@better-auth/utils": "0.4.0", + "@better-auth/core": "^1.6.23", + "@better-auth/utils": "0.4.2", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0" }, @@ -502,20 +502,20 @@ } }, "node_modules/@better-auth/telemetry": { - "version": "1.6.11", - "resolved": "https://registry.npmjs.org/@better-auth/telemetry/-/telemetry-1.6.11.tgz", - "integrity": "sha512-hsjDHc8MZbm6/AHeNdtywrWedXevnBjmdvnHTcZub+rTVjOv+Td0roI8USKuC6uUibmrl//2rJfVCsGbopihNA==", + "version": "1.6.23", + "resolved": "https://registry.npmjs.org/@better-auth/telemetry/-/telemetry-1.6.23.tgz", + "integrity": "sha512-/R2Kb+z2BpDOOWwVHqOk+c0VNpuwfCv4Hp5Yr9003WIZPax/zyNraGLB9CFE8qF2gZW8Dsz419k4I8CPrGzpDA==", "license": "MIT", "peerDependencies": { - "@better-auth/core": "^1.6.11", - "@better-auth/utils": "0.4.0", - "@better-fetch/fetch": "1.1.21" + "@better-auth/core": "^1.6.23", + "@better-auth/utils": "0.4.2", + "@better-fetch/fetch": "1.3.1" } }, "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==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@better-auth/utils/-/utils-0.4.2.tgz", + "integrity": "sha512-AUxrvu+HaaODsUyzDxFgwd/8RZ1yZaYo42LXKSrU2oGgR38pS1ij8nqQKNgtTWoYGpNevNXtCfgTy6loHveW9A==", "license": "MIT", "peer": true, "dependencies": { @@ -523,9 +523,10 @@ } }, "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==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@better-fetch/fetch/-/fetch-1.3.1.tgz", + "integrity": "sha512-ABkD1WhyfPZprKRQI3bhATjeiFuNWC9PXhfGWqL+sg/gKrM977oFrYkdb4msM3hgUGonr7KlOsOFT5TU2rht9g==", + "license": "MIT", "peer": true }, "node_modules/@bramus/specificity": { @@ -683,34 +684,10 @@ "node": ">=20.19.0" } }, - "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", "dev": true, "license": "MIT", "optional": true, @@ -719,9 +696,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", - "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", "cpu": [ "ppc64" ], @@ -735,9 +712,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", - "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", "cpu": [ "arm" ], @@ -751,9 +728,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", - "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", "cpu": [ "arm64" ], @@ -767,9 +744,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", - "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", "cpu": [ "x64" ], @@ -783,9 +760,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", - "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", "cpu": [ "arm64" ], @@ -799,9 +776,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", - "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", "cpu": [ "x64" ], @@ -815,9 +792,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", - "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", "cpu": [ "arm64" ], @@ -831,9 +808,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", - "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", "cpu": [ "x64" ], @@ -847,9 +824,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", - "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", "cpu": [ "arm" ], @@ -863,9 +840,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", - "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", "cpu": [ "arm64" ], @@ -879,9 +856,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", - "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", "cpu": [ "ia32" ], @@ -895,9 +872,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", - "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", "cpu": [ "loong64" ], @@ -911,9 +888,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", - "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", "cpu": [ "mips64el" ], @@ -927,9 +904,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", - "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", "cpu": [ "ppc64" ], @@ -943,9 +920,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", - "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", "cpu": [ "riscv64" ], @@ -959,9 +936,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", - "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", "cpu": [ "s390x" ], @@ -975,9 +952,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", - "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", "cpu": [ "x64" ], @@ -991,9 +968,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", - "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", "cpu": [ "arm64" ], @@ -1007,9 +984,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", - "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", "cpu": [ "x64" ], @@ -1023,9 +1000,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", - "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", "cpu": [ "arm64" ], @@ -1039,9 +1016,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", - "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", "cpu": [ "x64" ], @@ -1055,9 +1032,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", - "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", "cpu": [ "arm64" ], @@ -1071,9 +1048,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", - "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", "cpu": [ "x64" ], @@ -1087,9 +1064,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", - "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", "cpu": [ "arm64" ], @@ -1103,9 +1080,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", - "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", "cpu": [ "ia32" ], @@ -1119,9 +1096,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", - "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", "cpu": [ "x64" ], @@ -2295,9 +2272,9 @@ } }, "node_modules/@next/env": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.6.tgz", - "integrity": "sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw==", + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.10.tgz", + "integrity": "sha512-zLPxg9M0MEHmygpj5OuxjQ+vHMiy/K7cSp74G8ecYolmgUWw0RwN02tF56npup/+qaI8JB97hQgS/r2Hb6QwVA==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { @@ -2311,9 +2288,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.6.tgz", - "integrity": "sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg==", + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.10.tgz", + "integrity": "sha512-v9IdJCa0H0mbo+8z5zwUpOk1Vj7RjkcI5uNYf5Ws1y6szf/p3Mzl9hLaST8SCt6L9h8NGnruZcd2+o0NTNwDhA==", "cpu": [ "arm64" ], @@ -2327,9 +2304,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.6.tgz", - "integrity": "sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ==", + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.10.tgz", + "integrity": "sha512-17IS0jJRViROGmA9uGdNR8VPJpfbnaVG7E9qhso5jDLkmyd0lSDORWxbcKINzcFqzZqGwGtMSnrFRxBpuUYjLQ==", "cpu": [ "x64" ], @@ -2343,9 +2320,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.6.tgz", - "integrity": "sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w==", + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.10.tgz", + "integrity": "sha512-GRQRsRtuciNJvB54AvvuQTiq0oZtFwa1owQqtZD8wwnGpM2L39MV22kpI72YSXLKIyY40LC66EiLFv4PiicXxg==", "cpu": [ "arm64" ], @@ -2359,9 +2336,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.6.tgz", - "integrity": "sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA==", + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.10.tgz", + "integrity": "sha512-zkN9MQYS7UQBro+FnISUq1itaQjXI9xqISzuQ+2bc921NcJ1x4yPCqrn77tVN6/dOOXaaWVX3k6/bR07pPwK+A==", "cpu": [ "arm64" ], @@ -2375,9 +2352,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.6.tgz", - "integrity": "sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw==", + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.10.tgz", + "integrity": "sha512-iCVJnwvrPYECvA6WM/7+oo+OiTvedIKLxtCLAZP4xZR3nXa1zmzZyLPbYCmWvpd4CvMYF1EMTafd0ii3DygLvA==", "cpu": [ "x64" ], @@ -2391,9 +2368,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.6.tgz", - "integrity": "sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g==", + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.10.tgz", + "integrity": "sha512-ov2g4H0dHY9bPoOU83m91hWT7Iq5qy13bUnyyshLU3HGR1Ownn0X9QpmDPc5iIUaahTp7f7LeGAhV4DSFtackw==", "cpu": [ "x64" ], @@ -2407,9 +2384,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.6.tgz", - "integrity": "sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg==", + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.10.tgz", + "integrity": "sha512-DwAnhLX76HQiFFQNgWlcK+JzlnD1rZ+UK/WY0ZMI/deXpvgnesjNYrqcfo1JzBuz4Kf7o3brIBL0glI1junatA==", "cpu": [ "arm64" ], @@ -2423,9 +2400,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.6.tgz", - "integrity": "sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA==", + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.10.tgz", + "integrity": "sha512-0JXq3b85Jk9Jg4ntLUbXSPvoDw3gpZou7twuKdoFG2jOw635v7+IiXfTaa0TxVMyx78pUjnrVYwLgjKfX4e6/A==", "cpu": [ "x64" ], @@ -2511,18 +2488,18 @@ } }, "node_modules/@opentelemetry/semantic-conventions": { - "version": "1.41.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.41.1.tgz", - "integrity": "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==", + "version": "1.42.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.42.0.tgz", + "integrity": "sha512-icc5xCzndZfhuJMy5oqk5AvloWquR7jtae74qzpkKkhGp8BivK+oCcEXgGnjCdTfp8hA44l+w8gE8yYJbocJJw==", "license": "Apache-2.0", "engines": { "node": ">=14" } }, "node_modules/@oxc-project/types": { - "version": "0.130.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.130.0.tgz", - "integrity": "sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==", + "version": "0.139.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", + "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", "devOptional": true, "license": "MIT", "funding": { @@ -3594,9 +3571,9 @@ "license": "MIT" }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.1.tgz", - "integrity": "sha512-fJI3I0r3C3Oj/zdBCpaCmBRZYf07xpaq4yCfDDoSFm+beWNzbIl26puW8RraUdugoJw/95zerNOn6jasAhzSmg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", + "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", "cpu": [ "arm64" ], @@ -3611,9 +3588,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.1.tgz", - "integrity": "sha512-cKnAhWEsV7TPcA/5EAteDp6KcJZBQ2G+BqE7zayMMi7kMvwRsbv7WT9aOnn0WNl4SKEIf43vjS31iUPu80nzXg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", + "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", "cpu": [ "arm64" ], @@ -3628,9 +3605,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.1.tgz", - "integrity": "sha512-YKrVwQjIRBPo+5G/u03wGjbdy4q7pyzCe93DK9VJ7zkVmeg8LJ7GbgsiHWdR4xSoe4CAXRD7Bcjgbtr64bkXNg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", + "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", "cpu": [ "x64" ], @@ -3645,9 +3622,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.1.tgz", - "integrity": "sha512-z/oBsREo46SsFqBwYtFe0kpJeBijAT48O/WXLI4suiCLBkr03RTtTJMCzSdDd2znlh8VJizL09XVkQgk8IZonw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", + "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", "cpu": [ "x64" ], @@ -3662,9 +3639,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.1.tgz", - "integrity": "sha512-ik8q7GM11zxvYxFc2PeDcT6TBvhCQMaUxfph/M5l9sKuTs/Sjg3L+Byw0F7w0ZVLBZmx30P+gG0ECzzN+MFcmQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", + "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", "cpu": [ "arm" ], @@ -3679,9 +3656,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.1.tgz", - "integrity": "sha512-QoSx2EkyrrdZ6kcyE8stqZ62t0Yra8Fs5ia9lOxJrh6TMQJK7gQKmscdTHf7pOXKREKrVwOtJcQG3qVSfc866A==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", + "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", "cpu": [ "arm64" ], @@ -3696,9 +3673,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.1.tgz", - "integrity": "sha512-uwNwFpwKeNiZawfAWBgg0VIztPTV3ihhh1vV334h9ivnNLorxnQMU6Fz8wG1Zb4Qh9LC1/MkcyT3YlDXG3Rsgg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", + "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", "cpu": [ "arm64" ], @@ -3713,9 +3690,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.1.tgz", - "integrity": "sha512-zY1bul7OWr7DFBiJ++wofXvnr8B45ce3QsQUhKrIhXsygAh7bTkwyeM1bi1a2g5C/yC/N8TZyGDEoMfm/l9mpg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", + "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", "cpu": [ "ppc64" ], @@ -3730,9 +3707,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.1.tgz", - "integrity": "sha512-0frlsT/f4Ft6I7SMESTKnF3cZsdicQn1dCMkF/jT9wDLE+gGoiQfv1nmT9e+s7s/fekvvy6tZM2jHvI2tkbJDQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", + "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", "cpu": [ "s390x" ], @@ -3747,9 +3724,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.1.tgz", - "integrity": "sha512-XABVmGp9Tg0WspTVvwduTc4fpqy6JnAUrSQe6OuyqD/03nI7r0O9OWUkMIwFrjKAIqolvqoA4ZrJppgwE0Gxmw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", + "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", "cpu": [ "x64" ], @@ -3764,9 +3741,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.1.tgz", - "integrity": "sha512-bV4fzswuzVcKD90o/VM6QqKxnxlDq0g2BISDLNVmxrnhpv1DDbyPhCIjYfvzYLV+MvkKKnQt2Q6AO86SEBULUQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", + "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", "cpu": [ "x64" ], @@ -3781,9 +3758,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.1.tgz", - "integrity": "sha512-/Mh0Zhq3OP7fVs0kcQHZP6lZEthMGTaSf8UBQYSFEZDWGXXlEC+nJ6EqenaK2t4LBXMe3A+K/G2BVXXdtOr4PQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", + "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", "cpu": [ "arm64" ], @@ -3798,9 +3775,9 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.1.tgz", - "integrity": "sha512-+1xc9X45l8ufsBAm6Gjvx2qDRIY9lTVt0cgWNcJ+1gdhXvkbxePA60yRTwSTuXL09CMhyJmjpV7E3NoyxbqFQQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", + "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", "cpu": [ "wasm32" ], @@ -3808,23 +3785,23 @@ "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" }, "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@tybys/wasm-util": "^0.10.1" + "@tybys/wasm-util": "^0.10.3" }, "funding": { "type": "github", @@ -3836,9 +3813,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.1.tgz", - "integrity": "sha512-1D+UqZdfnuR+Jy1GgMJwi85bD40H21uNmOPRWQhw4oRSuolZ/B5rixZ45DK2KXOTCvmVCecauWgEhbw8bI7tOw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", + "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", "cpu": [ "arm64" ], @@ -3853,9 +3830,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.1.tgz", - "integrity": "sha512-INAycaWuhlOK3wk4mRHGsdgwYWmd9cChdPdE9bwWmy6rn9VqVNYNFGhOdXrofXUxwHIncSiPNb8tNm8knDVIeQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", + "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", "cpu": [ "x64" ], @@ -4127,29 +4104,6 @@ "node": ">=14.0.0" } }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { - "version": "1.10.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { - "version": "1.10.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { "version": "1.2.1", "dev": true, @@ -4333,9 +4287,9 @@ } }, "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", "dev": true, "license": "MIT", "optional": true, @@ -5566,26 +5520,26 @@ } }, "node_modules/better-auth": { - "version": "1.6.11", - "resolved": "https://registry.npmjs.org/better-auth/-/better-auth-1.6.11.tgz", - "integrity": "sha512-Wwt6+q07dwIhsp6XiM7L1qSXVUWBEtNl+eZvwM778CguFqDZFBN9Pt6LtFaHl55t8Z+Zc//5kxcbgDY8/79vFQ==", - "license": "MIT", - "dependencies": { - "@better-auth/core": "1.6.11", - "@better-auth/drizzle-adapter": "1.6.11", - "@better-auth/kysely-adapter": "1.6.11", - "@better-auth/memory-adapter": "1.6.11", - "@better-auth/mongo-adapter": "1.6.11", - "@better-auth/prisma-adapter": "1.6.11", - "@better-auth/telemetry": "1.6.11", - "@better-auth/utils": "0.4.0", - "@better-fetch/fetch": "1.1.21", + "version": "1.6.23", + "resolved": "https://registry.npmjs.org/better-auth/-/better-auth-1.6.23.tgz", + "integrity": "sha512-4vOaRd9UiKGKm9R+ej0jjU1es3MiJIiNc9Qq3VCnYqOZ4/nb5272QqTxWYoDxyUXl5x6A2x2we5KZKQO9teTQQ==", + "license": "MIT", + "dependencies": { + "@better-auth/core": "1.6.23", + "@better-auth/drizzle-adapter": "1.6.23", + "@better-auth/kysely-adapter": "1.6.23", + "@better-auth/memory-adapter": "1.6.23", + "@better-auth/mongo-adapter": "1.6.23", + "@better-auth/prisma-adapter": "1.6.23", + "@better-auth/telemetry": "1.6.23", + "@better-auth/utils": "0.4.2", + "@better-fetch/fetch": "1.3.1", "@noble/ciphers": "^2.1.1", "@noble/hashes": "^2.0.1", - "better-call": "1.3.5", + "better-call": "1.3.7", "defu": "^6.1.4", "jose": "^6.1.3", - "kysely": "^0.28.17", + "kysely": "^0.28.17 || ^0.29.0", "nanostores": "^1.1.1", "zod": "^4.3.6" }, @@ -5671,9 +5625,9 @@ } }, "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==", + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/better-call/-/better-call-1.3.7.tgz", + "integrity": "sha512-Al51/hjp2SSp6CRTa3F2ptcx4yQVS1xWKoY6jcVXqNYOap6mHFP2jUBn5EwIL4iIed1/Sq4hlQ+Umm6EflZG+w==", "license": "MIT", "peer": true, "dependencies": { @@ -6445,9 +6399,9 @@ } }, "node_modules/esbuild": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", - "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", "hasInstallScript": true, "license": "MIT", "bin": { @@ -6457,32 +6411,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.0", - "@esbuild/android-arm": "0.28.0", - "@esbuild/android-arm64": "0.28.0", - "@esbuild/android-x64": "0.28.0", - "@esbuild/darwin-arm64": "0.28.0", - "@esbuild/darwin-x64": "0.28.0", - "@esbuild/freebsd-arm64": "0.28.0", - "@esbuild/freebsd-x64": "0.28.0", - "@esbuild/linux-arm": "0.28.0", - "@esbuild/linux-arm64": "0.28.0", - "@esbuild/linux-ia32": "0.28.0", - "@esbuild/linux-loong64": "0.28.0", - "@esbuild/linux-mips64el": "0.28.0", - "@esbuild/linux-ppc64": "0.28.0", - "@esbuild/linux-riscv64": "0.28.0", - "@esbuild/linux-s390x": "0.28.0", - "@esbuild/linux-x64": "0.28.0", - "@esbuild/netbsd-arm64": "0.28.0", - "@esbuild/netbsd-x64": "0.28.0", - "@esbuild/openbsd-arm64": "0.28.0", - "@esbuild/openbsd-x64": "0.28.0", - "@esbuild/openharmony-arm64": "0.28.0", - "@esbuild/sunos-x64": "0.28.0", - "@esbuild/win32-arm64": "0.28.0", - "@esbuild/win32-ia32": "0.28.0", - "@esbuild/win32-x64": "0.28.0" + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" } }, "node_modules/escalade": { @@ -8109,10 +8063,20 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -8246,13 +8210,13 @@ } }, "node_modules/kysely": { - "version": "0.28.17", - "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.28.17.tgz", - "integrity": "sha512-nbD8lB9EB3wNdMhOCdx5Li8DxnLbvKByylRLcJ1h+4SkrowVeECAyZlyiKMThF7xFdRz0jSQ2MoJr+wXux2y0Q==", + "version": "0.29.3", + "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.29.3.tgz", + "integrity": "sha512-VHtBdW6XB/pgoTSqraM3UAa2rYoYdNXqnNPpX+8XXP+cwYbVEFuAp3HyPt1vpNfU9l7Y2kpUrA9QDPsy8uUqOQ==", "license": "MIT", "peer": true, "engines": { - "node": ">=20.0.0" + "node": ">=22.0.0" } }, "node_modules/language-subtag-registry": { @@ -8776,9 +8740,9 @@ } }, "node_modules/nanostores": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/nanostores/-/nanostores-1.3.0.tgz", - "integrity": "sha512-XPUa/jz+P1oJvN9VBxw4L9MtdFfaH3DAryqPssqhb2kXjmb9npz0dly6rCsgFWOPr4Yg9mTfM3MDZgZZ+7A3lA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/nanostores/-/nanostores-1.4.0.tgz", + "integrity": "sha512-i0tloweeudshAEuddpDxcg9Ik6pkPfVsHIgKyf143JrgG7/MOh0+q7BypdLXZPoOP7fOYt1eTcwGkyiVmhJFkA==", "funding": [ { "type": "github", @@ -8815,13 +8779,13 @@ "license": "MIT" }, "node_modules/next": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/next/-/next-16.2.6.tgz", - "integrity": "sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw==", + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/next/-/next-16.2.10.tgz", + "integrity": "sha512-2som5AVXb3kE6Yjine3/mNbBayYF58eguBWIVVUdr1y/L426xyVEgYxgBG+1QC34P2x5E+tcDup6XkuOAX3dCA==", "license": "MIT", "peer": true, "dependencies": { - "@next/env": "16.2.6", + "@next/env": "16.2.10", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", @@ -8835,14 +8799,14 @@ "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "16.2.6", - "@next/swc-darwin-x64": "16.2.6", - "@next/swc-linux-arm64-gnu": "16.2.6", - "@next/swc-linux-arm64-musl": "16.2.6", - "@next/swc-linux-x64-gnu": "16.2.6", - "@next/swc-linux-x64-musl": "16.2.6", - "@next/swc-win32-arm64-msvc": "16.2.6", - "@next/swc-win32-x64-msvc": "16.2.6", + "@next/swc-darwin-arm64": "16.2.10", + "@next/swc-darwin-x64": "16.2.10", + "@next/swc-linux-arm64-gnu": "16.2.10", + "@next/swc-linux-arm64-musl": "16.2.10", + "@next/swc-linux-x64-gnu": "16.2.10", + "@next/swc-linux-x64-musl": "16.2.10", + "@next/swc-win32-arm64-msvc": "16.2.10", + "@next/swc-win32-x64-msvc": "16.2.10", "sharp": "^0.34.5" }, "peerDependencies": { @@ -9235,9 +9199,9 @@ } }, "node_modules/postcss": { - "version": "8.5.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", - "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "version": "8.5.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", + "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", "devOptional": true, "funding": [ { @@ -9256,7 +9220,7 @@ "license": "MIT", "peer": true, "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -9600,13 +9564,13 @@ } }, "node_modules/rolldown": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.1.tgz", - "integrity": "sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", + "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", "devOptional": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.130.0", + "@oxc-project/types": "=0.139.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -9616,21 +9580,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.1", - "@rolldown/binding-darwin-arm64": "1.0.1", - "@rolldown/binding-darwin-x64": "1.0.1", - "@rolldown/binding-freebsd-x64": "1.0.1", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.1", - "@rolldown/binding-linux-arm64-gnu": "1.0.1", - "@rolldown/binding-linux-arm64-musl": "1.0.1", - "@rolldown/binding-linux-ppc64-gnu": "1.0.1", - "@rolldown/binding-linux-s390x-gnu": "1.0.1", - "@rolldown/binding-linux-x64-gnu": "1.0.1", - "@rolldown/binding-linux-x64-musl": "1.0.1", - "@rolldown/binding-openharmony-arm64": "1.0.1", - "@rolldown/binding-wasm32-wasi": "1.0.1", - "@rolldown/binding-win32-arm64-msvc": "1.0.1", - "@rolldown/binding-win32-x64-msvc": "1.0.1" + "@rolldown/binding-android-arm64": "1.1.5", + "@rolldown/binding-darwin-arm64": "1.1.5", + "@rolldown/binding-darwin-x64": "1.1.5", + "@rolldown/binding-freebsd-x64": "1.1.5", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", + "@rolldown/binding-linux-arm64-gnu": "1.1.5", + "@rolldown/binding-linux-arm64-musl": "1.1.5", + "@rolldown/binding-linux-ppc64-gnu": "1.1.5", + "@rolldown/binding-linux-s390x-gnu": "1.1.5", + "@rolldown/binding-linux-x64-gnu": "1.1.5", + "@rolldown/binding-linux-x64-musl": "1.1.5", + "@rolldown/binding-openharmony-arm64": "1.1.5", + "@rolldown/binding-wasm32-wasi": "1.1.5", + "@rolldown/binding-win32-arm64-msvc": "1.1.5", + "@rolldown/binding-win32-x64-msvc": "1.1.5" } }, "node_modules/rou3": { @@ -9755,9 +9719,9 @@ } }, "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==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.1.tgz", + "integrity": "sha512-vM9SUhjsUYs6UeJUmygc5Ofm5eQGe85riob5ju6XCgFGJI5PLV4nrDAQpQjd+LkFBpAkADn5BQQpZ9EUNkyLuA==", "license": "MIT" }, "node_modules/set-function-length": { @@ -10285,9 +10249,9 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "devOptional": true, "license": "MIT", "dependencies": { @@ -10626,9 +10590,9 @@ } }, "node_modules/undici": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", - "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", "dev": true, "license": "MIT", "engines": { @@ -10791,18 +10755,18 @@ } }, "node_modules/vite": { - "version": "8.0.13", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.13.tgz", - "integrity": "sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==", + "version": "8.1.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.4.tgz", + "integrity": "sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ==", "devOptional": true, "license": "MIT", "peer": true, "dependencies": { "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.14", - "rolldown": "1.0.1", - "tinyglobby": "^0.2.16" + "picomatch": "^4.0.5", + "postcss": "^8.5.16", + "rolldown": "~1.1.4", + "tinyglobby": "^0.2.17" }, "bin": { "vite": "bin/vite.js" @@ -10818,7 +10782,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.18", + "@vitejs/devtools": "^0.3.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -10870,9 +10834,9 @@ } }, "node_modules/vite/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "devOptional": true, "license": "MIT", "engines": { diff --git a/src/app/session-error/page.tsx b/src/app/session-error/page.tsx new file mode 100644 index 0000000..a67a8b5 --- /dev/null +++ b/src/app/session-error/page.tsx @@ -0,0 +1,35 @@ +import { handleSignOut } from "@/components/user-menu/actions"; + +/** + * Recovery page for authenticated-but-unusable sessions. + * + * `AuthWrapper` redirects here when a session exists but has no `userGuid` + * (the MP User_GUID). Such a session can't load an MP profile, so the header + * avatar/menu — and therefore the normal sign-out control — never render. + * This page gives the user an unconditional way out via `handleSignOut`. + * + * It lives outside the (web) route group, so it is NOT wrapped by AuthWrapper + * and cannot cause a redirect loop. + */ +export default function SessionErrorPage() { + return ( +
+
+

We couldn't load your account

+

+ Your sign-in completed, but it didn't include the Ministry Platform + user link we need to load your profile. Please sign out and sign in + again. If this keeps happening, contact your administrator. +

+
+ +
+
+
+ ); +} diff --git a/src/auth.test.ts b/src/auth.test.ts index 793d39e..c17c39a 100644 --- a/src/auth.test.ts +++ b/src/auth.test.ts @@ -1,4 +1,6 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { parseAdditionalUserInputFromProviderProfile } from 'better-auth/db'; +import { userAdditionalFields } from '@/lib/auth'; /** * Auth Tests @@ -186,6 +188,36 @@ describe('Auth - OAuth Configuration', () => { expect(mappedFields.userGuid).toBe('ab12cd34-ef56-7890-abcd-ef1234567890'); }); + /** + * Regression guard for the better-auth 1.6 upgrade incident. + * + * better-auth 1.6 changed `parseAdditionalUserInputFromProviderProfile` to + * strip any user additional field declared with `input: false` before the + * user record is created. Our `userGuid` field is populated server-side from + * the OAuth profile via `mapProfileToUser`, so `input: false` silently + * dropped it — leaving `session.user.userGuid` undefined and breaking every + * MP profile lookup (avatar, user menu, User_ID resolution). + * + * This test runs the REAL better-auth field-filtering function against our + * REAL field config, so it fails if either (a) someone flips `userGuid` back + * to `input: false`, or (b) a future better-auth upgrade changes how + * provider-profile fields are parsed. See .claude/references/auth.md. + */ + it('persists userGuid from the OAuth provider profile (better-auth 1.6 guard)', () => { + const guid = 'ab12cd34-ef56-7890-abcd-ef1234567890'; + const options = { user: { additionalFields: userAdditionalFields } }; + + // Mirrors the object better-auth builds from `mapProfileToUser`'s return + // before creating the user record. + const parsed = parseAdditionalUserInputFromProviderProfile( + options, + { userGuid: guid }, + 'create', + ); + + expect(parsed).toHaveProperty('userGuid', guid); + }); + it('should distinguish user.id (Better Auth internal) from userGuid (MP User_GUID)', () => { // Better Auth generates its own user.id (random nanoid-style) // The OAuth sub claim is stored as userGuid via additionalFields diff --git a/src/components/layout/auth-wrapper.test.tsx b/src/components/layout/auth-wrapper.test.tsx new file mode 100644 index 0000000..ffaff35 --- /dev/null +++ b/src/components/layout/auth-wrapper.test.tsx @@ -0,0 +1,78 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; + +/** + * AuthWrapper guard tests. + * + * AuthWrapper is the server-side auth guard for the (web) route group. It must: + * - redirect unauthenticated requests to /signin + * - redirect authenticated-but-broken sessions (no userGuid) to /session-error, + * so a user whose session lacks a userGuid still has a way to sign out. This + * is the safety net behind the better-auth 1.6 userGuid regression — see + * src/auth.test.ts and .claude/references/auth.md. + * - render children for healthy sessions + */ + +const { mockGetSession, mockRedirect } = vi.hoisted(() => ({ + mockGetSession: vi.fn(), + // Mirror next/navigation's redirect(), which halts execution by throwing. + mockRedirect: vi.fn((url: string) => { + throw new Error(`REDIRECT:${url}`); + }), +})); + +vi.mock("@/lib/auth", () => ({ + auth: { api: { getSession: mockGetSession } }, +})); +vi.mock("next/headers", () => ({ + headers: vi.fn(async () => new Headers()), +})); +vi.mock("next/navigation", () => ({ + redirect: mockRedirect, +})); + +import { AuthWrapper } from "./auth-wrapper"; + +describe("AuthWrapper", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("redirects to /signin when there is no session", async () => { + mockGetSession.mockResolvedValue(null); + + await expect(AuthWrapper({ children: null })).rejects.toThrow("REDIRECT:/signin"); + expect(mockRedirect).toHaveBeenCalledWith("/signin"); + }); + + it("redirects to /session-error when the session has no userGuid", async () => { + mockGetSession.mockResolvedValue({ + user: { id: "ba-internal-id", name: "No Guid", email: "x@example.com" }, + }); + + await expect(AuthWrapper({ children: null })).rejects.toThrow( + "REDIRECT:/session-error", + ); + expect(mockRedirect).toHaveBeenCalledWith("/session-error"); + }); + + it("redirects to /session-error when userGuid is null", async () => { + mockGetSession.mockResolvedValue({ + user: { id: "ba-internal-id", userGuid: null }, + }); + + await expect(AuthWrapper({ children: null })).rejects.toThrow( + "REDIRECT:/session-error", + ); + }); + + it("renders children when the session has a userGuid", async () => { + mockGetSession.mockResolvedValue({ + user: { id: "ba-internal-id", userGuid: "ab12cd34-ef56-7890-abcd-ef1234567890" }, + }); + + const result = await AuthWrapper({ children: "CONTENT" }); + + expect(mockRedirect).not.toHaveBeenCalled(); + expect(result).toBeTruthy(); + }); +}); diff --git a/src/components/layout/auth-wrapper.tsx b/src/components/layout/auth-wrapper.tsx index e74af28..69371eb 100644 --- a/src/components/layout/auth-wrapper.tsx +++ b/src/components/layout/auth-wrapper.tsx @@ -11,5 +11,16 @@ export async function AuthWrapper({ children }: { children: React.ReactNode }) { redirect("/signin"); } + // A session without a userGuid is unusable: every MP lookup keys off userGuid, + // and without it the header avatar/menu never renders — which leaves the user + // with no way to even sign out (the trap behind the better-auth 1.6 regression). + // Route these broken sessions to a recovery page that CAN sign them out, + // rather than rendering a dead app. `/session-error` lives outside the (web) + // route group, so it is not wrapped by AuthWrapper and cannot redirect-loop. + const userGuid = (session.user as { userGuid?: string | null }).userGuid; + if (!userGuid) { + redirect("/session-error"); + } + return <>{children}; } diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 08744c2..3eab042 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -7,6 +7,26 @@ import { sanitizeGuid } from "@/lib/providers/ministry-platform/utils/filter-san const mpBaseUrl = process.env.MINISTRY_PLATFORM_BASE_URL!; +/** + * Custom fields added to the Better Auth `user` record. + * + * `userGuid` (the MP User_GUID / OAuth `sub`) MUST keep `input: true`. It is + * populated server-side from the OAuth profile via `mapProfileToUser` below. + * As of better-auth 1.6, `parseAdditionalUserInputFromProviderProfile` strips + * any additional field declared with `input: false` BEFORE the user record is + * created — so `input: false` silently drops `userGuid`, which breaks every MP + * profile lookup (avatar, user menu, User_ID resolution). There is no + * user-facing form that sets this field, so allowing input carries no practical + * risk here. `src/auth.test.ts` guards this against future regressions. + */ +export const userAdditionalFields = { + userGuid: { + type: "string" as const, + required: false, + input: true, + }, +}; + // Process-wide cache of User_GUID → MP User_ID. customSession runs on every // getSession() call, so without a cache each request would do a dp_Users // lookup. Mapping is stable per user, so an unbounded Map is fine in practice. @@ -51,13 +71,7 @@ const options = { storeAccountCookie: true, }, user: { - additionalFields: { - userGuid: { - type: "string" as const, - required: false, - input: false, - }, - }, + additionalFields: userAdditionalFields, }, plugins: [ genericOAuth({