diff --git a/app/api/admin/bootstrap/route.ts b/app/api/admin/bootstrap/route.ts index 026d65a..db0a7a4 100644 --- a/app/api/admin/bootstrap/route.ts +++ b/app/api/admin/bootstrap/route.ts @@ -4,7 +4,7 @@ import { getAdminClaims, normalizeEmail, } from "@/lib/admin-auth"; -import { getFirebaseAdminAuth } from "@/lib/firebase-admin"; +import { getFirebaseAdminAuth } from "@/lib/firebase-admin-auth"; import { logAdminAudit } from "@/lib/admin-audit"; import { ensureAdminUser, updateAdminUser } from "@/lib/admin-users"; import { getBearerToken, unauthorizedResponse } from "@/lib/api-auth"; diff --git a/app/api/admin/invitations/[id]/resend/route.ts b/app/api/admin/invitations/[id]/resend/route.ts index be4da59..46826e4 100644 --- a/app/api/admin/invitations/[id]/resend/route.ts +++ b/app/api/admin/invitations/[id]/resend/route.ts @@ -14,7 +14,7 @@ import { recordInvitationEmailAttempt, } from "@/lib/admin-invitations"; import { canResendInvitation } from "@/lib/admin-invitation-resend-policy"; -import { getFirebaseAdminDb } from "@/lib/firebase-admin"; +import { getFirebaseAdminDb } from "@/lib/firebase-admin-db"; import { serializeAdminInvitation } from "@/lib/admin-serializers"; import { badRequestResponse, diff --git a/app/api/admin/invitations/accept/route.ts b/app/api/admin/invitations/accept/route.ts index 7fc15a2..f6a5313 100644 --- a/app/api/admin/invitations/accept/route.ts +++ b/app/api/admin/invitations/accept/route.ts @@ -4,7 +4,8 @@ import { normalizeEmail, verifyAdminIdToken, } from "@/lib/admin-auth"; -import { getFirebaseAdminAuth, getFirebaseAdminDb } from "@/lib/firebase-admin"; +import { getFirebaseAdminAuth } from "@/lib/firebase-admin-auth"; +import { getFirebaseAdminDb } from "@/lib/firebase-admin-db"; import { logAdminAudit } from "@/lib/admin-audit"; import { getPendingInvitationByEmail } from "@/lib/admin-invitations"; import { getAdminUser } from "@/lib/admin-users"; diff --git a/app/api/admin/users/[uid]/route.ts b/app/api/admin/users/[uid]/route.ts index 78eed06..4ae45e3 100644 --- a/app/api/admin/users/[uid]/route.ts +++ b/app/api/admin/users/[uid]/route.ts @@ -3,7 +3,7 @@ import { normalizeEmail, requireSuperAdminActor, } from "@/lib/admin-auth"; -import { getFirebaseAdminAuth } from "@/lib/firebase-admin"; +import { getFirebaseAdminAuth } from "@/lib/firebase-admin-auth"; import { logAdminAudit } from "@/lib/admin-audit"; import { countActiveSuperAdmins, diff --git a/lib/admin-audit.ts b/lib/admin-audit.ts index 935f017..2d9bcff 100644 --- a/lib/admin-audit.ts +++ b/lib/admin-audit.ts @@ -1,5 +1,5 @@ import "server-only"; -import { getFirebaseAdminDb } from "@/lib/firebase-admin"; +import { getFirebaseAdminDb } from "@/lib/firebase-admin-db"; import { dropUndefinedValues } from "@/lib/admin-audit-common"; import type { AdminAuditRecord } from "@/lib/admin-types"; import { Timestamp } from "firebase-admin/firestore"; diff --git a/lib/admin-auth.ts b/lib/admin-auth.ts index 7e2c31d..7c738f6 100644 --- a/lib/admin-auth.ts +++ b/lib/admin-auth.ts @@ -1,5 +1,5 @@ import "server-only"; -import { getFirebaseAdminAuth } from "@/lib/firebase-admin"; +import { getFirebaseAdminAuth } from "@/lib/firebase-admin-auth"; import { getAdminClaims, getProtectedAdminEmail, diff --git a/lib/admin-invitations.ts b/lib/admin-invitations.ts index a18cc86..387e44c 100644 --- a/lib/admin-invitations.ts +++ b/lib/admin-invitations.ts @@ -1,5 +1,5 @@ import "server-only"; -import { getFirebaseAdminDb } from "@/lib/firebase-admin"; +import { getFirebaseAdminDb } from "@/lib/firebase-admin-db"; import { normalizeEmail } from "@/lib/admin-common"; import type { AdminInvitation, AdminRole } from "@/lib/admin-types"; import { FieldValue, Timestamp } from "firebase-admin/firestore"; diff --git a/lib/admin-users.ts b/lib/admin-users.ts index 437dfdd..1f1e001 100644 --- a/lib/admin-users.ts +++ b/lib/admin-users.ts @@ -1,5 +1,5 @@ import "server-only"; -import { getFirebaseAdminDb } from "@/lib/firebase-admin"; +import { getFirebaseAdminDb } from "@/lib/firebase-admin-db"; import { buildExistingAdminLoginUpdate, buildNewAdminUserRecord, diff --git a/lib/firebase-admin.ts b/lib/firebase-admin-app.ts similarity index 78% rename from lib/firebase-admin.ts rename to lib/firebase-admin-app.ts index 1f1a2b6..aa4a086 100644 --- a/lib/firebase-admin.ts +++ b/lib/firebase-admin-app.ts @@ -1,7 +1,5 @@ import "server-only"; import { App, cert, getApps, initializeApp } from "firebase-admin/app"; -import { getAuth } from "firebase-admin/auth"; -import { getFirestore } from "firebase-admin/firestore"; let adminApp: App | undefined; @@ -43,11 +41,3 @@ export function getFirebaseAdminApp(): App { return adminApp; } - -export function getFirebaseAdminAuth() { - return getAuth(getFirebaseAdminApp()); -} - -export function getFirebaseAdminDb() { - return getFirestore(getFirebaseAdminApp()); -} diff --git a/lib/firebase-admin-auth.ts b/lib/firebase-admin-auth.ts new file mode 100644 index 0000000..90411de --- /dev/null +++ b/lib/firebase-admin-auth.ts @@ -0,0 +1,9 @@ +import "server-only"; +import { getAuth } from "firebase-admin/auth"; +import { getFirebaseAdminApp } from "@/lib/firebase-admin-app"; + +// Auth is isolated in its own module: only routes that actually verify +// tokens or manage users pay for the jwks-rsa -> jose dependency graph. +export function getFirebaseAdminAuth() { + return getAuth(getFirebaseAdminApp()); +} diff --git a/lib/firebase-admin-db.ts b/lib/firebase-admin-db.ts new file mode 100644 index 0000000..9e1f44e --- /dev/null +++ b/lib/firebase-admin-db.ts @@ -0,0 +1,11 @@ +import "server-only"; +import { getFirestore } from "firebase-admin/firestore"; +import { getFirebaseAdminApp } from "@/lib/firebase-admin-app"; + +// Firestore lives in its own module so Firestore-only routes never import +// firebase-admin/auth — its jwks-rsa -> ESM-only jose chain is the failure +// that caused the nfl-picks Vercel runtime outage (ERR_REQUIRE_ESM despite +// a clean build). +export function getFirebaseAdminDb() { + return getFirestore(getFirebaseAdminApp()); +} diff --git a/lib/trade-leads.ts b/lib/trade-leads.ts index 446230c..fd41855 100644 --- a/lib/trade-leads.ts +++ b/lib/trade-leads.ts @@ -1,6 +1,6 @@ import "server-only"; import { FieldValue } from "firebase-admin/firestore"; -import { getFirebaseAdminDb } from "@/lib/firebase-admin"; +import { getFirebaseAdminDb } from "@/lib/firebase-admin-db"; import { getResendClient } from "@/lib/resend"; import { getDefaultFromEmail } from "@/lib/resend-config"; import { logError, logInfo } from "@/lib/log"; diff --git a/next.config.ts b/next.config.ts index 4ebabd9..bc39b58 100644 --- a/next.config.ts +++ b/next.config.ts @@ -41,6 +41,11 @@ const mediaCacheHeaders = [ const nextConfig: NextConfig = { pageExtensions: ["ts", "tsx", "md", "mdx"], poweredByHeader: false, + // Keep firebase-admin outside the server bundle: its auth graph pulls + // jwks-rsa -> jose, and bundling the Node-specific chain into a + // serverless chunk caused a Vercel runtime outage class (ERR_REQUIRE_ESM) + // in other projects. Load the real node_modules at runtime instead. + serverExternalPackages: ["firebase-admin"], images: { formats: ["image/avif", "image/webp"], deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840], diff --git a/package-lock.json b/package-lock.json index 9cc692b..357b380 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,7 @@ "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "^16.3.5", - "firebase-admin": "^13.10.0", + "firebase-admin": "^14.5.0", "firebase-tools": "15.30.2", "klaro": "0.7.21", "playwright": "^1.63.0", @@ -2242,36 +2242,250 @@ } }, "node_modules/@google-cloud/firestore": { - "version": "7.11.6", - "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-7.11.6.tgz", - "integrity": "sha512-EW/O8ktzwLfyWBOsNuhRoMi8lrC3clHM5LVFhGvO1HCsLozCOOXRAlHrYBoE6HL42Sc8yYMuCb2XqcnJ4OOEpw==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-9.2.0.tgz", + "integrity": "sha512-4BI2686ygMkef0Pe4QnX9hnWeGCUjaAP5m+Ow5/yNWwGxGadO741Ct28MRXW9czg0pnvz919INKbQdCgUMcfeA==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { - "@opentelemetry/api": "^1.3.0", - "fast-deep-equal": "^3.1.1", + "@google-cloud/firestore-api": "^0.2.0", + "@opentelemetry/api": "^1.9.0", + "fast-deep-equal": "^3.1.3", "functional-red-black-tree": "^1.0.1", - "google-gax": "^4.3.3", - "protobufjs": "^7.2.6" + "google-gax": "^6.0.1", + "protobufjs": "^7.5.3" }, "engines": { - "node": ">=14.0.0" + "node": ">=22" + } + }, + "node_modules/@google-cloud/firestore-api": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore-api/-/firestore-api-0.2.0.tgz", + "integrity": "sha512-wfHJPZmLqzpy6FN6U+7HkkP/AKhGsbHSpEy/qL2TyI3/pKRAvpamQxZ2qvps5hMRgAjfuHCim+oeJj9RveCaqg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "google-gax": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@google-cloud/firestore-api/node_modules/@grpc/grpc-js": { + "version": "1.14.5", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.5.tgz", + "integrity": "sha512-7VZM+SVdEcUUqSQeNI3zM8Qs/BhQKZndPo2h5VkYkAM8Iz0wJIa8mKV5ekQGqG8UUsnkQ0NMxIxwkIHYvj0qOw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@google-cloud/firestore-api/node_modules/@grpc/proto-loader": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.1.tgz", + "integrity": "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@google-cloud/firestore-api/node_modules/gaxios": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.3.1.tgz", + "integrity": "sha512-kB3rzJV7d9juLZh8/56QTXCwQfxyhdOMdyYk1HdQKFtF8TJTDTZQJtixWIwXdE9Jji91mC41DUNpjleo4L4eAQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@google-cloud/firestore-api/node_modules/gcp-metadata": { + "version": "8.1.4", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.4.tgz", + "integrity": "sha512-iJ9KMsiu+xKtNRX0PmGLSaIU3bUBAyzWTyqKemKPzNPsmmsBCQYmlNg+brEbES7IHSXtdVwzBPzx1vz3FAaipw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "gaxios": "7.1.3", + "google-logging-utils": "1.1.3", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@google-cloud/firestore-api/node_modules/gcp-metadata/node_modules/gaxios": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", + "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2", + "rimraf": "^5.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@google-cloud/firestore-api/node_modules/google-auth-library": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.5.0.tgz", + "integrity": "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.0.0", + "gcp-metadata": "^8.0.0", + "google-logging-utils": "^1.0.0", + "gtoken": "^8.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@google-cloud/firestore-api/node_modules/google-gax": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-5.0.8.tgz", + "integrity": "sha512-M4vpZcXQIC1gqIVGQ7eaU3jXQA6zecStyTXu514TYfThlgSurYJOxHZo9fzU6hAgwPWvuEynAVHWyaIk80VeEA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@grpc/grpc-js": "^1.12.6", + "@grpc/proto-loader": "^0.8.0", + "duplexify": "^4.1.3", + "google-auth-library": "10.5.0", + "google-logging-utils": "1.1.3", + "node-fetch": "^3.3.2", + "object-hash": "^3.0.0", + "proto3-json-serializer": "3.0.4", + "protobufjs": "^7.5.4", + "retry-request": "^8.0.2", + "rimraf": "^5.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@google-cloud/firestore-api/node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/firestore-api/node_modules/gtoken": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz", + "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "gaxios": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@google-cloud/firestore-api/node_modules/proto3-json-serializer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-3.0.4.tgz", + "integrity": "sha512-E1sbAYg3aEbXrq0n1ojJkRHQJGE1kaE/O6GLA94y8rnJBfgvOPTOd1b9hOceQK1FFZI9qMh1vBERCyO2ifubcw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "protobufjs": "^7.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@google-cloud/firestore-api/node_modules/retry-request": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-8.0.4.tgz", + "integrity": "sha512-pI6/7eabUYkZxamkOq0g0uMxKLLGnjzhefY+vL8bVXag5rto4OU2YBTPytWLuHH7aEKD6fn7kJycQfid0Mwnkw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "extend": "^3.0.2", + "teeny-request": "^10.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@google-cloud/firestore-api/node_modules/teeny-request": { + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-10.1.4.tgz", + "integrity": "sha512-R1Cg4Vu0UULeDfHL/kjABLaTW++9yD/B6n2g48y5dJ04hsEaxcfmAqbNDzNsbqAYJyIpZafjklLG9YxRu9uzOg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2", + "stream-events": "^1.0.5" + }, + "engines": { + "node": ">=18" } }, "node_modules/@google-cloud/paginator": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz", - "integrity": "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-7.1.0.tgz", + "integrity": "sha512-PNCXdAQgNoQODZuqyhmkN2nUpB2q9UFzawZ1oyCDyEyhcheRoCKJJhSKhEIeKpM/NCLnC6Q4We4HEkGCwNDnCA==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { - "arrify": "^2.0.0", "extend": "^3.0.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=22" } }, "node_modules/@google-cloud/precise-date": { @@ -2285,25 +2499,25 @@ } }, "node_modules/@google-cloud/projectify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz", - "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-6.1.0.tgz", + "integrity": "sha512-tKAx9q6vTtbgZcqCojKWK8k6Dy2XBsE7BkNrrBsblxNXrKImYnljqeGfYh9qnTrlTjC9F19pTyCkGcl3pFMW/w==", "dev": true, "license": "Apache-2.0", "optional": true, "engines": { - "node": ">=14.0.0" + "node": ">=22" } }, "node_modules/@google-cloud/promisify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz", - "integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-6.1.0.tgz", + "integrity": "sha512-H+Rytzi5zYMyoAmU/zgYLgq7fJ9FY4QQEg4k6gfFx/RY1COJ2xxyIfe8bZ62lqEbWM1DJH7IYETqTFmqNtuUhQ==", "dev": true, "license": "Apache-2.0", "optional": true, "engines": { - "node": ">=14" + "node": ">=22" } }, "node_modules/@google-cloud/pubsub": { @@ -2561,20 +2775,6 @@ "node": ">=18" } }, - "node_modules/@google-cloud/pubsub/node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/@google-cloud/pubsub/node_modules/proto3-json-serializer": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-3.0.4.tgz", @@ -2776,20 +2976,6 @@ "node": ">=18" } }, - "node_modules/@google-cloud/sql/node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/@google-cloud/sql/node_modules/proto3-json-serializer": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-3.0.4.tgz", @@ -2834,16 +3020,16 @@ } }, "node_modules/@google-cloud/storage": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.21.0.tgz", - "integrity": "sha512-l+IFTkd+6Y5LoAuXyYCKNAKtw/Ci+rAMqgdTB1jv4iZiLhw0rtq+0qjIRbBizXkNzEFmXiXUW0H7sZQQvk1ffA==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-8.2.0.tgz", + "integrity": "sha512-7ckgSjeEprV92VrlLjpYFA3PFerflvFtF7Ea86oQXGNCprGZq5rtpAVK0zgWnCy+44mLjms42M7MiWa433gz2A==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { - "@google-cloud/paginator": "^5.0.0", - "@google-cloud/projectify": "^4.0.0", - "@google-cloud/promisify": "<4.1.0", + "@google-cloud/paginator": "^7.0.1", + "@google-cloud/projectify": "^6.0.1", + "@google-cloud/promisify": "^6.0.1", "abort-controller": "^3.0.0", "async-retry": "^1.3.3", "duplexify": "^4.1.3", @@ -2853,11 +3039,11 @@ "html-entities": "^2.5.2", "mime": "^3.0.0", "p-limit": "^3.0.1", - "retry-request": "^7.0.0", - "teeny-request": "^9.0.0" + "retry-request": "^9.0.1", + "teeny-request": "^11.0.1" }, "engines": { - "node": ">=14" + "node": ">=22" } }, "node_modules/@googleapis/sqladmin": { @@ -8136,17 +8322,6 @@ "tailwindcss": "4.3.3" } }, - "node_modules/@tootallnate/once": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.1.tgz", - "integrity": "sha512-HqmEUIGRJ5fSXchkVgR5F7qn48bDBzv0kWj/Kfu5e6uci4UlEeng4331LnBkWffb++Ei3FOVLxo8JJWMFBDMeQ==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 10" - } - }, "node_modules/@tootallnate/quickjs-emscripten": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", @@ -8246,14 +8421,6 @@ "tslib": "^2.4.0" } }, - "node_modules/@types/caseless": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", - "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -8311,14 +8478,6 @@ "@types/node": "*" } }, - "node_modules/@types/long": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/@types/mdast": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", @@ -8368,20 +8527,6 @@ "@types/react": "^19.2.0" } }, - "node_modules/@types/request": { - "version": "2.48.13", - "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.13.tgz", - "integrity": "sha512-FGJ6udDNUCjd19pp0Q3iTiDkwhYup7J8hpMW9c4k53NrccQFFWKRho6hvtPPEhnXWKvukfwAlB6DbDz4yhH5Gg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@types/caseless": "*", - "@types/node": "*", - "@types/tough-cookie": "*", - "form-data": "^2.5.5" - } - }, "node_modules/@types/statuses": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.6.tgz", @@ -8389,14 +8534,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/tough-cookie": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", - "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/@types/triple-beam": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", @@ -13487,16 +13624,6 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, - "node_modules/farmhash-modern": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/farmhash-modern/-/farmhash-modern-1.1.0.tgz", - "integrity": "sha512-6ypT4XfgqJk/F3Yuv4SX26I3doUjt0GTG4a+JgWxXQpxXzTBq8fPUeGHfcYMMDPHJHm3yPOSjaeBwBGAHWXCdA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -13577,9 +13704,9 @@ "license": "BSD-3-Clause" }, "node_modules/fast-xml-builder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.3.0.tgz", - "integrity": "sha512-F74cZEdCvuw9P41GAC3rod4X04jjWGM1JPEv/GWSqFTWLsdyMSBMBMlm9Hk3GLBgLBbdBNY8yee0pQh2RBVESQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.3.1.tgz", + "integrity": "sha512-pIM/1n3ntFXKYrUZwW7QCK0gAW7XY+wzj1YMIV3tLDvPj/V+zTGJK5e3/4WJfwj0qWw2ElNXiTixda/R+3YSug==", "dev": true, "funding": [ { @@ -13595,9 +13722,9 @@ } }, "node_modules/fast-xml-parser": { - "version": "5.10.1", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.10.1.tgz", - "integrity": "sha512-IEMIf7298kXuZSRFoGfMYrl7is8LpavODgbNz1cwIudv7KwVFnuU+UsMporfq6PD6aXSlawZlARiA3UywCTfMw==", + "version": "5.11.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.11.1.tgz", + "integrity": "sha512-TBw6K/fxoQGGjCmZDw9w/ZwP3uDcnTM4YH/g+PFRWr8sbe5idXtxNN6vITh4+1ruCZaho6uBFurElsA7F0zzgw==", "dev": true, "funding": [ { @@ -13612,7 +13739,7 @@ "fast-xml-builder": "^1.2.0", "is-unsafe": "^2.0.0", "path-expression-matcher": "^1.6.2", - "strnum": "^2.4.1", + "strnum": "^2.4.2", "xml-naming": "^0.3.0" }, "bin": { @@ -13789,27 +13916,26 @@ } }, "node_modules/firebase-admin": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-13.10.0.tgz", - "integrity": "sha512-rbuCrJvYRwqBqvbccMS8fj/x2zsaMisdf5RQbRzQzr14Rbq9r2UlpuBHqWAwrO6c9dIRF56xF/xoepXsD5yDuQ==", + "version": "14.5.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-14.5.0.tgz", + "integrity": "sha512-DElmgmaH9aPXu2dWXGQiDoan33UfTnbfAAKaue4fwEZxmpdaNrTk5DmuFn+vVDki2KZ6TiaC0IHRWcEzcDxB3Q==", "dev": true, "license": "Apache-2.0", "dependencies": { "@fastify/busboy": "^3.0.0", - "@firebase/database-compat": "^2.0.0", - "@firebase/database-types": "^1.0.6", - "farmhash-modern": "^1.1.0", + "@firebase/database-compat": "^2.1.4", + "@firebase/database-types": "^1.0.20", "fast-deep-equal": "^3.1.1", - "google-auth-library": "^10.6.1", + "google-auth-library": "^11.1.0", "jsonwebtoken": "^9.0.0", - "jwks-rsa": "^3.1.0" + "jwks-rsa": "^4.0.1" }, "engines": { - "node": ">=18" + "node": ">=22" }, "optionalDependencies": { - "@google-cloud/firestore": "^7.11.0", - "@google-cloud/storage": "^7.19.0" + "@google-cloud/firestore": "^9.1.0", + "@google-cloud/storage": "^8.1.0" } }, "node_modules/firebase-admin/node_modules/@firebase/app-check-interop-types": { @@ -13926,9 +14052,9 @@ } }, "node_modules/firebase-admin/node_modules/gaxios": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.2.0.tgz", - "integrity": "sha512-CUVb4wcYe+771XevyH6HtGmXFAGGKkIC3kswAP8Z1JCe0j80JMaTPZH930DWFrvo0atjh18Arc0pEyUCWa5bfg==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.3.1.tgz", + "integrity": "sha512-kB3rzJV7d9juLZh8/56QTXCwQfxyhdOMdyYk1HdQKFtF8TJTDTZQJtixWIwXdE9Jji91mC41DUNpjleo4L4eAQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -13941,46 +14067,46 @@ } }, "node_modules/firebase-admin/node_modules/gcp-metadata": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", - "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-9.0.4.tgz", + "integrity": "sha512-p6WvbXOhfwnVtv/MyPq5kBiVhfV/qglMIxwwTVNEI1Ha1HmOgz5uHYHtYO6SfGckFaXe8blA11IJJ3wqXmvmBA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "gaxios": "^7.0.0", - "google-logging-utils": "^1.0.0", + "gaxios": "^7.1.3", + "google-logging-utils": "^2.0.0", "json-bigint": "^1.0.0" }, "engines": { - "node": ">=18" + "node": ">=22" } }, "node_modules/firebase-admin/node_modules/google-auth-library": { - "version": "10.9.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.9.0.tgz", - "integrity": "sha512-xtvUqvINPhTaBm7nXqlYPcrMHJPm1lCNdSovxnKKhTm+4JsvQ+KGVYJViLoH9Yxu8w+T0Qv5HubzYT9BLrppJg==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-11.1.0.tgz", + "integrity": "sha512-YIBq5KNoHQrZ9PM4PeodDBqqBGlSTIxh1zibN8u3zNIO9UJg8gvKuCpdKV7fWXhsZ2zRkU6FhGrgKNgLJ2IRgw==", "dev": true, "license": "Apache-2.0", "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.1.4", - "gcp-metadata": "8.1.2", - "google-logging-utils": "1.1.3", + "gcp-metadata": "^9.0.0", + "google-logging-utils": "^2.0.0", "jws": "^4.0.0" }, "engines": { - "node": ">=18" + "node": ">=22" } }, "node_modules/firebase-admin/node_modules/google-logging-utils": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", - "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-2.0.1.tgz", + "integrity": "sha512-HMhaQghlOTvbcb3c4T5jmmOMtG3JUF1iOQMezaJXL86CDS+Tm2vHd0IeLFRAx3+ewd+bo9E1HFHoy17X5aJa9A==", "dev": true, "license": "Apache-2.0", "engines": { - "node": ">=14" + "node": ">=22" } }, "node_modules/firebase-tools": { @@ -14705,81 +14831,37 @@ "dev": true, "license": "MIT" }, - "node_modules/for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/form-data": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.6.tgz", - "integrity": "sha512-Ogz/E85h9tlfJzpI6TuFpGcHZFhLrb9Gw8wq9v40CxSCPnv7ahKr6Xgtkn0KYCDQJ8DNn5VoMO8EXr9V5PadyA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.4", - "mime-types": "^2.1.35", - "safe-buffer": "^5.2.1" - }, - "engines": { - "node": ">= 0.12" - } - }, - "node_modules/form-data/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "dev": true, "license": "MIT", - "optional": true, + "dependencies": { + "is-callable": "^1.2.7" + }, "engines": { - "node": ">= 0.6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/form-data/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, - "license": "MIT", - "optional": true, + "license": "ISC", "dependencies": { - "mime-db": "1.52.0" + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" }, "engines": { - "node": ">= 0.6" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/formdata-polyfill": { @@ -15302,34 +15384,33 @@ } }, "node_modules/google-gax": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.6.1.tgz", - "integrity": "sha512-V6eky/xz2mcKfAd1Ioxyd6nmA61gao3n01C+YeuIwu3vzM9EDR6wcVzMSIbLMDXWeoi9SHYctXuKYC5uJUT3eQ==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-6.7.0.tgz", + "integrity": "sha512-v9QQ0c3MFCqG5K0/wfao7Bkk3knNxfnBHRb0UAssQvZJsf9DitRrguMrrSAfFcaEf3L6DHflbUyfW2NUt9aKFw==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { - "@grpc/grpc-js": "^1.10.9", - "@grpc/proto-loader": "^0.7.13", - "@types/long": "^4.0.0", - "abort-controller": "^3.0.0", - "duplexify": "^4.0.0", - "google-auth-library": "^9.3.0", - "node-fetch": "^2.7.0", + "@grpc/grpc-js": "^1.12.6", + "@grpc/proto-loader": "^0.8.0", + "@opentelemetry/api": "^1.9.0", + "duplexify": "^4.1.3", + "google-auth-library": "^11.0.0", + "google-logging-utils": "^2.0.0", + "node-fetch": "^3.3.2", "object-hash": "^3.0.0", - "proto3-json-serializer": "^2.0.2", - "protobufjs": "^7.3.2", - "retry-request": "^7.0.0", - "uuid": "^9.0.1" + "proto3-json-serializer": "^4.0.0", + "protobufjs": "^7.5.4", + "retry-request": "^9.0.0" }, "engines": { - "node": ">=14" + "node": ">=22" } }, "node_modules/google-gax/node_modules/@grpc/grpc-js": { - "version": "1.14.4", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.4.tgz", - "integrity": "sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==", + "version": "1.14.5", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.5.tgz", + "integrity": "sha512-7VZM+SVdEcUUqSQeNI3zM8Qs/BhQKZndPo2h5VkYkAM8Iz0wJIa8mKV5ekQGqG8UUsnkQ0NMxIxwkIHYvj0qOw==", "dev": true, "license": "Apache-2.0", "optional": true, @@ -15341,7 +15422,7 @@ "node": ">=12.10.0" } }, - "node_modules/google-gax/node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": { + "node_modules/google-gax/node_modules/@grpc/proto-loader": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.1.tgz", "integrity": "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==", @@ -15361,26 +15442,66 @@ "node": ">=6" } }, - "node_modules/google-gax/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "node_modules/google-gax/node_modules/gaxios": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.3.1.tgz", + "integrity": "sha512-kB3rzJV7d9juLZh8/56QTXCwQfxyhdOMdyYk1HdQKFtF8TJTDTZQJtixWIwXdE9Jji91mC41DUNpjleo4L4eAQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "optional": true, "dependencies": { - "whatwg-url": "^5.0.0" + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" }, "engines": { - "node": "4.x || >=6.0.0" + "node": ">=18" + } + }, + "node_modules/google-gax/node_modules/gcp-metadata": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-9.0.4.tgz", + "integrity": "sha512-p6WvbXOhfwnVtv/MyPq5kBiVhfV/qglMIxwwTVNEI1Ha1HmOgz5uHYHtYO6SfGckFaXe8blA11IJJ3wqXmvmBA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "gaxios": "^7.1.3", + "google-logging-utils": "^2.0.0", + "json-bigint": "^1.0.0" }, - "peerDependencies": { - "encoding": "^0.1.0" + "engines": { + "node": ">=22" + } + }, + "node_modules/google-gax/node_modules/google-auth-library": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-11.1.0.tgz", + "integrity": "sha512-YIBq5KNoHQrZ9PM4PeodDBqqBGlSTIxh1zibN8u3zNIO9UJg8gvKuCpdKV7fWXhsZ2zRkU6FhGrgKNgLJ2IRgw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.1.4", + "gcp-metadata": "^9.0.0", + "google-logging-utils": "^2.0.0", + "jws": "^4.0.0" }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "engines": { + "node": ">=22" + } + }, + "node_modules/google-gax/node_modules/google-logging-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-2.0.1.tgz", + "integrity": "sha512-HMhaQghlOTvbcb3c4T5jmmOMtG3JUF1iOQMezaJXL86CDS+Tm2vHd0IeLFRAx3+ewd+bo9E1HFHoy17X5aJa9A==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=22" } }, "node_modules/google-logging-utils": { @@ -15783,33 +15904,17 @@ "license": "MIT" }, "node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/http-proxy-agent/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "debug": "4" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, "engines": { - "node": ">= 6.0.0" + "node": ">= 14" } }, "node_modules/https-proxy-agent": { @@ -16707,9 +16812,9 @@ } }, "node_modules/is-unsafe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-2.0.0.tgz", - "integrity": "sha512-2LdV822R+wmI86unXA93WCFpL6g+av8ynWk0nrHyJqGop5VoocYsSLFgN8jrfalT6iGeLNM4KXuVSsULP53kEA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-2.0.2.tgz", + "integrity": "sha512-HgbIHPBH0KHHCcjLfGsCvhtPTVxjaAZlXjwdz7/GQC40SjSe4sfQsar8J5VFo8JOSbarkpV0OLG95bbaNd9aAQ==", "dev": true, "funding": [ { @@ -17164,32 +17269,43 @@ } }, "node_modules/jwks-rsa": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.2.2.tgz", - "integrity": "sha512-BqTyEDV+lS8F2trk3A+qJnxV5Q9EqKCBJOPti3W97r7qTympCZjb7h2X6f2kc+0K3rsSTY1/6YG2eaXKoj497w==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-4.1.0.tgz", + "integrity": "sha512-sbkByqyATKYJP5F4RXj03N5TUNC0QLTjCAZvwTzC4BwJZ8e0/cWxN8YROnyUth2g1/ONWi4eSFHeu6oYalrc3Q==", "dev": true, "license": "MIT", "dependencies": { "@types/jsonwebtoken": "^9.0.4", "debug": "^4.3.4", - "jose": "^4.15.4", + "jose": "^6.1.3", "limiter": "^1.1.5", - "lru-memoizer": "^2.2.0" + "lru-cache": "^11.0.0", + "lru-memoizer": "^3.0.0" }, "engines": { - "node": ">=14" + "node": "^20.19.0 || ^22.12.0 || >= 23.0.0" } }, "node_modules/jwks-rsa/node_modules/jose": { - "version": "4.15.9", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", - "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", + "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==", "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" } }, + "node_modules/jwks-rsa/node_modules/lru-cache": { + "version": "11.5.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/jws": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", @@ -17865,36 +17981,26 @@ } }, "node_modules/lru-memoizer": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz", - "integrity": "sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-3.0.0.tgz", + "integrity": "sha512-m83w/cYXLdUIboKSPxzPAGfYnk+vqeDYXuoSrQRw1q+yVEd8IXhvMufN8Q5TIPe7e2jyX4SRNrDJI2Skw1yznQ==", "dev": true, "license": "MIT", "dependencies": { "lodash.clonedeep": "^4.5.0", - "lru-cache": "6.0.0" + "lru-cache": "^11.0.1" } }, "node_modules/lru-memoizer/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "version": "11.5.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, + "license": "BlueOak-1.0.0", "engines": { - "node": ">=10" + "node": "20 || >=22" } }, - "node_modules/lru-memoizer/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, "node_modules/lsofi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lsofi/-/lsofi-2.0.0.tgz", @@ -19903,20 +20009,6 @@ "node": ">= 14" } }, - "node_modules/pac-proxy-agent/node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/pac-resolver": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", @@ -20552,17 +20644,17 @@ "license": "ISC" }, "node_modules/proto3-json-serializer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz", - "integrity": "sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-4.0.2.tgz", + "integrity": "sha512-VUQlb/J2WjOG8BKhdLwJUz+MKxSMtBGYh+TaNmRtiYVhlIzXjzeAGIx7gtc3lXlA2iURzfF2+cXaQvC2ofwzpg==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { - "protobufjs": "^7.2.5" + "protobufjs": "^7.5.4" }, "engines": { - "node": ">=14.0.0" + "node": ">=22" } }, "node_modules/protobufjs": { @@ -20622,20 +20714,6 @@ "node": ">= 14" } }, - "node_modules/proxy-agent/node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/proxy-agent/node_modules/lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", @@ -21395,19 +21473,18 @@ } }, "node_modules/retry-request": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", - "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-9.0.1.tgz", + "integrity": "sha512-4kGHEBl0ME6gR+8QB1sPMyg58jUxLUcCZNeqLDrfqzk0eWQN8XJFmeu7fmZCjlZCRW0S2u64Ik1HkO/0sWgCYQ==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@types/request": "^2.48.8", "extend": "^3.0.2", - "teeny-request": "^9.0.0" + "teeny-request": "^11.0.0" }, "engines": { - "node": ">=14" + "node": ">=22" } }, "node_modules/rettime": { @@ -22747,9 +22824,9 @@ } }, "node_modules/strnum": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.1.tgz", - "integrity": "sha512-M9eUSMT2dCB2cTNPG7UYj6KuK7RJR2SN2+yCV/fTW3xzTCS6EaGZ5pSMgDIjB7r8zSfTGk+dvvn9rTjpVS9Mwg==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.2.tgz", + "integrity": "sha512-rDG3Ah4TV0k1hWvLSzkZtMmLN9+eS+h3knq4MP6A42Y3Yh5qGNnOUs1jJkoSr8FG5dsL28c7KgkIBzSEykqtuw==", "dev": true, "funding": [ { @@ -23085,72 +23162,20 @@ "license": "MIT" }, "node_modules/teeny-request": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", - "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-11.0.1.tgz", + "integrity": "sha512-bNr5j2YjSdajgCVsp+8JVjRf1uHIbpNISLeKp/7V1NnVMX3Gh6H/kECNGlm2Q3I68ACODQ0iv6aZ3Rvm8WgLGA==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.9", - "stream-events": "^1.0.5", - "uuid": "^9.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/teeny-request/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/teeny-request/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/teeny-request/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "whatwg-url": "^5.0.0" + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2", + "stream-events": "^1.0.5" }, "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "node": ">=22" } }, "node_modules/teex": { diff --git a/package.json b/package.json index e9fb421..a965cef 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "build": "next build", "start": "next start", "lint": "eslint", - "test": "node --import tsx --test \"tests/**/*.test.ts\"", + "check:admin-import": "node scripts/check-admin-import.mjs", + "test": "npm run check:admin-import && node --import tsx --test \"tests/**/*.test.ts\"", "test:rules": "firebase emulators:exec --only firestore,storage --project demo-deepdivebrewing-web \"node --import tsx --test rules-tests/*.test.ts\"", "test:smoke": "npm run build && playwright test", "check:md-links": "node scripts/check-md-links.mjs", @@ -50,7 +51,7 @@ "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "^16.3.5", - "firebase-admin": "^13.10.0", + "firebase-admin": "^14.5.0", "firebase-tools": "15.30.2", "klaro": "0.7.21", "playwright": "^1.63.0", @@ -62,6 +63,9 @@ "typescript": "^5" }, "overrides": { + "jwks-rsa": { + "jose": "5.10.0" + }, "@sentry/node-core": { "@opentelemetry/core": "^2.11.0" }, diff --git a/scripts/check-admin-import.mjs b/scripts/check-admin-import.mjs new file mode 100644 index 0000000..b468149 --- /dev/null +++ b/scripts/check-admin-import.mjs @@ -0,0 +1,43 @@ +// Server-runtime smoke check for the firebase-admin dependency chain. +// +// Why this exists: a jose/jwks-rsa combination under firebase-admin can +// install, typecheck, and `next build` cleanly yet crash when the SDK is +// imported in the real Node server runtime (the nfl-picks Vercel outage, +// ERR_REQUIRE_ESM). This script loads the same entrypoints through plain +// Node ESM — no bundler, no test transform — which is the closest +// credential-free signal to what actually runs on the server. +// +// Run: node scripts/check-admin-import.mjs (wired into `npm test`) + +import { readFileSync } from "node:fs"; +import { findPackageJSON } from "node:module"; + +const entrypoints = { + "firebase-admin/app": ["initializeApp", "cert", "getApps"], + "firebase-admin/auth": ["getAuth"], + "firebase-admin/firestore": ["getFirestore"], +}; + +// firebase-admin's exports map does not expose ./package.json, so locate +// it via Node's own package resolution. +const pkgJsonPath = findPackageJSON("firebase-admin", import.meta.url); +const { version } = JSON.parse(readFileSync(pkgJsonPath, "utf8")); +if (!version.startsWith("14.")) { + console.error( + `firebase-admin@${version} loaded — expected the 14.x line. ` + + `See docs/TECHNICAL.md and issue #141 before changing this.`, + ); + process.exit(1); +} + +for (const [entry, names] of Object.entries(entrypoints)) { + const mod = await import(entry); + for (const name of names) { + if (typeof mod[name] !== "function") { + console.error(`${entry}: expected export "${name}" to be a function`); + process.exit(1); + } + } +} + +console.log(`firebase-admin@${version}: server import chain OK`); diff --git a/tests/lib/firebase-admin-graph.test.ts b/tests/lib/firebase-admin-graph.test.ts new file mode 100644 index 0000000..d8c2444 --- /dev/null +++ b/tests/lib/firebase-admin-graph.test.ts @@ -0,0 +1,89 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { createRequire, findPackageJSON } from "node:module"; +import test from "node:test"; + +/** + * Guards the module split that keeps Firestore-only routes from importing + * the Admin Auth dependency graph (jwks-rsa -> jose), the failure class + * behind the nfl-picks Vercel outage: a firebase-admin/auth import that + * installs and builds cleanly but crashes the deployed serverless + * function with ERR_REQUIRE_ESM. + * + * Intentionally no firebase-admin mocks — these tests exercise the real + * installed dependency tree through Node's own resolution. + */ + +const require = createRequire(import.meta.url); + +test("firebase-admin resolves a 14.x release", () => { + const pkgPath = findPackageJSON("firebase-admin", import.meta.url); + assert.ok(pkgPath, "firebase-admin package.json not found"); + const { version } = JSON.parse(readFileSync(pkgPath, "utf8")); + assert.match(version, /^14\./, `expected firebase-admin 14.x, found ${version}`); +}); + +test("firebase-admin/auth loads through require() without ERR_REQUIRE_ESM", () => { + // The exact production failure: evaluating the auth graph (jwks-rsa -> + // jose) threw ERR_REQUIRE_ESM inside the Vercel server bundle. + assert.doesNotThrow(() => { + require("firebase-admin/auth"); + }); +}); + +test("jwks-rsa resolves a require()-able jose build", () => { + // jwks-rsa is CommonJS and calls require("jose"); jose@6 ships no CJS + // build, which crashed this repo's Vercel Preview with ERR_REQUIRE_ESM + // (verified on PR #142 before the pin was added). The package.json + // override pins jwks-rsa's jose to 5.x — this guards that resolution. + const jwksRsaDir = require("path").dirname( + require.resolve("jwks-rsa/package.json"), + ); + const joseEntry = require.resolve("jose", { paths: [jwksRsaDir] }); + const josePkg = require(require.resolve("jose/package.json", { + paths: [jwksRsaDir], + })) as { version: string }; + + assert.ok( + Number(josePkg.version.split(".")[0]) < 6, + `jose@${josePkg.version} must be < 6 (6.x is ESM-only); resolved entry: ${joseEntry}`, + ); + assert.doesNotThrow( + () => require(joseEntry), + `jwks-rsa's jose must be require()-able; resolved: ${joseEntry}`, + ); +}); + +test("firebase-admin entrypoints import in plain Node", async () => { + const app = await import("firebase-admin/app"); + assert.equal(typeof app.initializeApp, "function"); + const auth = await import("firebase-admin/auth"); + assert.equal(typeof auth.getAuth, "function"); + const firestore = await import("firebase-admin/firestore"); + assert.equal(typeof firestore.getFirestore, "function"); +}); + +test("Firestore-only sources never import the admin Auth graph", () => { + const firestoreOnlySources = [ + "lib/firebase-admin-app.ts", + "lib/firebase-admin-db.ts", + "lib/admin-audit.ts", + "lib/admin-invitations.ts", + "lib/admin-users.ts", + "lib/trade-leads.ts", + "app/api/admin/invitations/[id]/resend/route.ts", + ]; + + // Quoted module specifiers only, so explanatory comments mentioning the + // auth package do not trip the guard. + const authSpecifier = /["'][^"']*(firebase-admin\/auth|firebase-admin-auth)["']/; + + for (const rel of firestoreOnlySources) { + const source = readFileSync(rel, "utf8").replace(/\/\/.*$/gm, ""); + assert.equal( + authSpecifier.test(source), + false, + `${rel} must not import the Admin Auth module graph`, + ); + } +});