From bff5f65b193beee2737390ead79b63571ecc378e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 13:30:26 +0000 Subject: [PATCH 1/5] Initial plan From 5595476aaae5f2ad5c1434850c26503fe2b0240a Mon Sep 17 00:00:00 2001 From: Suvam-paul145 Date: Fri, 27 Feb 2026 20:38:57 +0530 Subject: [PATCH 2/5] - Add OAuthProviderError class for structured error codes related to OAuth provider failures. - Enhance getOIDCUserData and triggerOauthFlow functions to handle network and provider errors gracefully. - Update login callback to log and respond to OAuth provider errors with appropriate messages. - Introduce SignInButton component for user sign-in with error handling via query parameters. - Create integration tests for OAuth provider failures to ensure correct error propagation and responses. - Add mongodb-memory-server dependency for testing purposes. --- frontend/package-lock.json | 797 +++++++++++++----- frontend/package.json | 1 + .../src/lib/components/SignInButton.svelte | 52 ++ frontend/src/lib/server/auth.spec.ts | 234 +++++ frontend/src/lib/server/auth.ts | 102 ++- frontend/src/routes/login/callback/+server.ts | 35 +- .../callback/auth-provider-failure.spec.ts | 170 ++++ 7 files changed, 1165 insertions(+), 226 deletions(-) create mode 100644 frontend/src/lib/components/SignInButton.svelte create mode 100644 frontend/src/lib/server/auth.spec.ts create mode 100644 frontend/src/routes/login/callback/auth-provider-failure.spec.ts diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c3269a712..1d455b251 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -78,6 +78,7 @@ "isomorphic-dompurify": "2.13.0", "js-yaml": "^4.1.0", "minimist": "^1.2.8", + "mongodb-memory-server": "^11.0.1", "playwright": "^1.55.1", "prettier": "^3.5.3", "prettier-plugin-svelte": "^3.2.6", @@ -111,6 +112,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -1033,31 +1035,6 @@ "node": ">=18.0.0" } }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/runtime": { "version": "7.27.6", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", @@ -1199,6 +1176,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1215,6 +1193,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1231,6 +1210,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1247,6 +1227,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1263,6 +1244,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1279,6 +1261,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1295,6 +1278,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1311,6 +1295,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1327,6 +1312,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1343,6 +1329,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1359,6 +1346,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1375,6 +1363,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1391,6 +1380,7 @@ "cpu": [ "mips64el" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1407,6 +1397,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1423,6 +1414,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1439,6 +1431,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1455,6 +1448,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1471,6 +1465,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1487,6 +1482,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1503,6 +1499,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1519,6 +1516,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1535,6 +1533,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1551,6 +1550,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1567,6 +1567,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1583,6 +1584,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2349,16 +2351,6 @@ "url": "https://opencollective.com/libvips" } }, - "node_modules/@internationalized/date": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.10.0.tgz", - "integrity": "sha512-oxDR/NTEJ1k+UFVQElaNIk65E/Z83HK1z1WI3lQyhTtnNg4R5oVXaPzK3jcpKG8UHKDVuDQHzn+wsxSz8RP3aw==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@swc/helpers": "^0.5.0" - } - }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -2565,6 +2557,16 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.6.tgz", + "integrity": "sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2623,6 +2625,7 @@ "version": "1.0.0-next.29", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, "license": "MIT" }, "node_modules/@resvg/resvg-js": { @@ -2943,6 +2946,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2956,6 +2960,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2969,6 +2974,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2982,6 +2988,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2995,6 +3002,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3008,6 +3016,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3021,6 +3030,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3034,6 +3044,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3047,6 +3058,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3060,6 +3072,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3073,6 +3086,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3086,6 +3100,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3099,6 +3114,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3112,6 +3128,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3125,6 +3142,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3138,6 +3156,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3151,6 +3170,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3164,6 +3184,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3177,6 +3198,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3190,6 +3212,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3802,13 +3825,14 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@sveltejs/acorn-typescript": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz", "integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==", + "dev": true, "license": "MIT", "peerDependencies": { "acorn": "^8.9.0" @@ -3834,7 +3858,7 @@ "version": "2.50.2", "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.50.2.tgz", "integrity": "sha512-875hTUkEbz+MyJIxWbQjfMaekqdmEKUUfR7JyKcpfMRZqcGyrO9Gd+iS1D/Dx8LpE5FEtutWGOtlAh4ReSAiOA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", @@ -3877,7 +3901,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-5.1.0.tgz", "integrity": "sha512-wojIS/7GYnJDYIg1higWj2ROA6sSRWvcR1PO/bqEyFr/5UZah26c8Cz4u0NaqjPeVltzsVpt2Tm8d2io0V+4Tw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", @@ -3899,7 +3923,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-4.0.1.tgz", "integrity": "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "debug": "^4.3.7" @@ -3913,16 +3937,6 @@ "vite": "^6.0.0" } }, - "node_modules/@swc/helpers": { - "version": "0.5.17", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", - "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.8.0" - } - }, "node_modules/@tailwindcss/typography": { "version": "0.5.16", "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.16.tgz", @@ -3939,40 +3953,6 @@ "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, - "node_modules/@testing-library/dom": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", - "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.3.0", - "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "pretty-format": "^27.0.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@testing-library/user-event": { - "version": "14.6.1", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", - "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12", - "npm": ">=6" - }, - "peerDependencies": { - "@testing-library/dom": ">=7.21.4" - } - }, "node_modules/@tokenizer/inflate": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz", @@ -4006,17 +3986,11 @@ "node": ">= 10" } }, - "node_modules/@types/aria-query": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", - "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "license": "MIT", - "peer": true - }, "node_modules/@types/chai": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", + "dev": true, "license": "MIT", "dependencies": { "@types/deep-eql": "*" @@ -4026,13 +4000,14 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@types/deep-eql": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, "license": "MIT" }, "node_modules/@types/dompurify": { @@ -4049,6 +4024,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, "license": "MIT" }, "node_modules/@types/js-yaml": { @@ -4172,6 +4148,23 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/whatwg-url": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, "node_modules/@types/yazl": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/@types/yazl/-/yazl-3.3.0.tgz", @@ -4387,46 +4380,11 @@ "dev": true, "license": "ISC" }, - "node_modules/@vitest/browser": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-3.2.2.tgz", - "integrity": "sha512-LJk8ZhCGhgG6G6jFFJ9LX83ibRY8FszLuu9zPaYFDrcHbBwNXwt1v06HRs/vHVYxwjw3/BGzSIgn9Et2P6rCiA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@testing-library/dom": "^10.4.0", - "@testing-library/user-event": "^14.6.1", - "@vitest/mocker": "3.2.2", - "@vitest/utils": "3.2.2", - "magic-string": "^0.30.17", - "sirv": "^3.0.1", - "tinyrainbow": "^2.0.0", - "ws": "^8.18.2" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "playwright": "*", - "vitest": "3.2.2", - "webdriverio": "^7.0.0 || ^8.0.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "playwright": { - "optional": true - }, - "safaridriver": { - "optional": true - }, - "webdriverio": { - "optional": true - } - } - }, "node_modules/@vitest/expect": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.2.tgz", "integrity": "sha512-ipHw0z669vEMjzz3xQE8nJX1s0rQIb7oEl4jjl35qWTwm/KIHERIg/p/zORrjAaZKXfsv7IybcNGHwhOOAPMwQ==", + "dev": true, "license": "MIT", "dependencies": { "@types/chai": "^5.2.2", @@ -4443,6 +4401,7 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.2.tgz", "integrity": "sha512-jKojcaRyIYpDEf+s7/dD3LJt53c0dPfp5zCPXz9H/kcGrSlovU/t1yEaNzM9oFME3dcd4ULwRI/x0Po1Zf+LTw==", + "dev": true, "license": "MIT", "dependencies": { "@vitest/spy": "3.2.2", @@ -4469,6 +4428,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.0" @@ -4478,6 +4438,7 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.2.tgz", "integrity": "sha512-FY4o4U1UDhO9KMd2Wee5vumwcaHw7Vg4V7yR4Oq6uK34nhEJOmdRYrk3ClburPRUA09lXD/oXWZ8y/Sdma0aUQ==", + "dev": true, "license": "MIT", "dependencies": { "tinyrainbow": "^2.0.0" @@ -4490,6 +4451,7 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.2.tgz", "integrity": "sha512-GYcHcaS3ejGRZYed2GAkvsjBeXIEerDKdX3orQrBJqLRiea4NSS9qvn9Nxmuy1IwIB+EjFOaxXnX79l8HFaBwg==", + "dev": true, "license": "MIT", "dependencies": { "@vitest/utils": "3.2.2", @@ -4503,12 +4465,14 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, "license": "MIT" }, "node_modules/@vitest/snapshot": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.2.tgz", "integrity": "sha512-aMEI2XFlR1aNECbBs5C5IZopfi5Lb8QJZGGpzS8ZUHML5La5wCbrbhLOVSME68qwpT05ROEEOAZPRXFpxZV2wA==", + "dev": true, "license": "MIT", "dependencies": { "@vitest/pretty-format": "3.2.2", @@ -4523,12 +4487,14 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, "license": "MIT" }, "node_modules/@vitest/spy": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.2.tgz", "integrity": "sha512-6Utxlx3o7pcTxvp0u8kUiXtRFScMrUg28KjB3R2hon7w4YqOFAEA9QwzPVVS1QNL3smo4xRNOpNZClRVfpMcYg==", + "dev": true, "license": "MIT", "dependencies": { "tinyspy": "^4.0.3" @@ -4541,6 +4507,7 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.2.tgz", "integrity": "sha512-qJYMllrWpF/OYfWHP32T31QCaLa3BAzT/n/8mNGhPdVcjY+JYazQFO1nsJvXU12Kp1xMpNY4AGuljPTNjQve6A==", + "dev": true, "license": "MIT", "dependencies": { "@vitest/pretty-format": "3.2.2", @@ -4612,6 +4579,7 @@ "version": "8.14.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -4791,16 +4759,6 @@ "dev": true, "license": "Python-2.0" }, - "node_modules/aria-query": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", - "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "dequal": "^2.0.3" - } - }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -4815,11 +4773,22 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" } }, + "node_modules/async-mutex": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", + "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -4882,17 +4851,48 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">= 0.4" } }, + "node_modules/b4a": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz", + "integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/bare-events": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", + "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -5070,6 +5070,16 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bson": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-7.2.0.tgz", + "integrity": "sha512-YCEo7KjMlbNlyHhz7zAZNDpIpQbd+wOEHJYezv0nMYTn4x31eIUM2yomNNubclAt63dObUzKHWsBLJ9QcZNSnQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=20.19.0" + } + }, "node_modules/bson-objectid": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/bson-objectid/-/bson-objectid-2.0.4.tgz", @@ -5101,6 +5111,16 @@ "ieee754": "^1.2.1" } }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -5114,6 +5134,7 @@ "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5158,6 +5179,19 @@ "node": ">=6" } }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", @@ -5200,6 +5234,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", + "dev": true, "license": "MIT", "dependencies": { "assertion-error": "^2.0.1", @@ -5216,6 +5251,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -5232,6 +5268,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 16" @@ -5408,7 +5445,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -5588,6 +5625,7 @@ "version": "5.0.2", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -5604,7 +5642,7 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -5650,7 +5688,7 @@ "version": "5.6.2", "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.2.tgz", "integrity": "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/didyoumean": { @@ -5691,13 +5729,6 @@ "node": ">=6.0.0" } }, - "node_modules/dom-accessibility-api": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", - "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "license": "MIT", - "peer": true - }, "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -5902,6 +5933,7 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, "license": "MIT" }, "node_modules/es-object-atoms": { @@ -5935,6 +5967,7 @@ "version": "0.25.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", + "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -6229,6 +6262,7 @@ "version": "1.4.7", "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.4.7.tgz", "integrity": "sha512-0ZxW6guTF/AeKeKi7he93lmgv7Hx7giD1tBrOeVqkqsZGQJd2/kfnL7LdIsr9FT/AtkBK9XeDTov+gxprBqdEg==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" @@ -6307,6 +6341,16 @@ "node": ">=0.8.x" } }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, "node_modules/eventsource": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", @@ -6367,6 +6411,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz", "integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=12.0.0" @@ -6495,6 +6540,13 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -6599,6 +6651,7 @@ "version": "6.4.5", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz", "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", + "dev": true, "license": "MIT", "peerDependencies": { "picomatch": "^3 || ^4" @@ -6679,6 +6732,24 @@ "url": "https://opencollective.com/express" } }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "license": "MIT", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -6718,6 +6789,27 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -7030,6 +7122,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -7741,13 +7834,6 @@ "node": ">=10" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT", - "peer": true - }, "node_modules/js-yaml": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", @@ -7903,7 +7989,7 @@ "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -8056,6 +8142,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "dev": true, "license": "MIT" }, "node_modules/locate-path": { @@ -8188,6 +8275,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", + "dev": true, "license": "MIT" }, "node_modules/lru-cache": { @@ -8226,11 +8314,38 @@ "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/marked": { "version": "12.0.2", "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz", @@ -8261,6 +8376,13 @@ "node": ">= 0.8" } }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "dev": true, + "license": "MIT" + }, "node_modules/merge-descriptors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", @@ -8412,11 +8534,138 @@ "dev": true, "license": "MIT" }, + "node_modules/mongodb": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.1.0.tgz", + "integrity": "sha512-kMfnKunbolQYwCIyrkxNJFB4Ypy91pYqua5NargS/f8ODNSJxT03ZU3n1JqL4mCzbSih8tvmMEMLpKTT7x5gCg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.3.0", + "bson": "^7.1.1", + "mongodb-connection-string-url": "^7.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.806.0", + "@mongodb-js/zstd": "^7.0.0", + "gcp-metadata": "^7.0.1", + "kerberos": "^7.0.0", + "mongodb-client-encryption": ">=7.0.0 <7.1.0", + "snappy": "^7.3.2", + "socks": "^2.8.6" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-7.0.1.tgz", + "integrity": "sha512-h0AZ9A7IDVwwHyMxmdMXKy+9oNlF0zFoahHiX3vQ8e3KFcSP3VmsmfvtRSuLPxmyv2vjIDxqty8smTgie/SNRQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^13.0.0", + "whatwg-url": "^14.1.0" + }, + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/mongodb-memory-server": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/mongodb-memory-server/-/mongodb-memory-server-11.0.1.tgz", + "integrity": "sha512-nUlKovSJZBh7q5hPsewFRam9H66D08Ne18nyknkNalfXMPtK1Og3kOcuqQhcX88x/pghSZPIJHrLbxNFW3OWiw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "mongodb-memory-server-core": "11.0.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/mongodb-memory-server-core": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/mongodb-memory-server-core/-/mongodb-memory-server-core-11.0.1.tgz", + "integrity": "sha512-IcIb2S9Xf7Lmz43Z1ZujMqNg7PU5Q7yn+4wOnu7l6pfeGPkEmlqzV1hIbroVx8s4vXhPB1oMGC1u8clW7aj3Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-mutex": "^0.5.0", + "camelcase": "^6.3.0", + "debug": "^4.4.3", + "find-cache-dir": "^3.3.2", + "follow-redirects": "^1.15.11", + "https-proxy-agent": "^7.0.6", + "mongodb": "^7.0.0", + "new-find-package-json": "^2.0.0", + "semver": "^7.7.3", + "tar-stream": "^3.1.7", + "tslib": "^2.8.1", + "yauzl": "^3.2.0" + }, + "engines": { + "node": ">=20.19.0" + } + }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -8426,6 +8675,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -8488,6 +8738,19 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "license": "MIT" }, + "node_modules/new-find-package-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/new-find-package-json/-/new-find-package-json-2.0.0.tgz", + "integrity": "sha512-lDcBsjBSMlj3LXH2v/FW3txlh2pYTjmbOXPYJD93HI5EwuLzI11tdHSIpUMmfq/IOsldj4Ps8M8flhm+pCK4Ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">=12.22.0" + } + }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -8824,6 +9087,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -8998,6 +9271,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 14.16" @@ -9016,6 +9290,13 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -9026,6 +9307,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -9135,6 +9417,75 @@ "node": ">=16.20.0" } }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/pkg-types": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", @@ -9158,7 +9509,7 @@ "version": "1.55.1", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.1.tgz", "integrity": "sha512-cJW4Xd/G3v5ovXtJJ52MAOclqeac9S/aGGgRzLabuF8TnIb6xHvMzKIa6JmrRzUkeXJgfL1MhukP0NK6l39h3A==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "dependencies": { "playwright-core": "1.55.1" @@ -9177,7 +9528,7 @@ "version": "1.55.1", "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.1.tgz", "integrity": "sha512-Z6Mh9mkwX+zxSlHqdr5AOcJnfp+xUWLCt9uKV18fhzA8eyxUd8NUWzAjxUh55RZKSYwDGX0cfaySdhZJGMoJ+w==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" @@ -9536,34 +9887,6 @@ } } }, - "node_modules/pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -9761,13 +10084,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "license": "MIT", - "peer": true - }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -9929,6 +10245,7 @@ "version": "4.41.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.1.tgz", "integrity": "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==", + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.7" @@ -9968,6 +10285,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, "license": "MIT" }, "node_modules/router": { @@ -10043,7 +10361,7 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "mri": "^1.1.0" @@ -10136,9 +10454,9 @@ "license": "BSD-3-Clause" }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -10221,7 +10539,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.0.1.tgz", "integrity": "sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/setprototypeof": { @@ -10366,6 +10684,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, "license": "ISC" }, "node_modules/signal-exit": { @@ -10393,6 +10712,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.1.tgz", "integrity": "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==", + "dev": true, "license": "MIT", "dependencies": { "@polka/url": "^1.0.0-next.24", @@ -10474,6 +10794,16 @@ "node": ">=0.10.0" } }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, "node_modules/split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", @@ -10493,6 +10823,7 @@ "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, "license": "MIT" }, "node_modules/statuses": { @@ -10508,8 +10839,21 @@ "version": "3.9.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "dev": true, "license": "MIT" }, + "node_modules/streamx": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", + "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -10788,6 +11132,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -10812,6 +11157,7 @@ "version": "5.33.14", "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.33.14.tgz", "integrity": "sha512-kRlbhIlMTijbFmVDQFDeKXPLlX1/ovXwV0I162wRqQhRcygaqDIcu1d/Ese3H2uI+yt3uT8E7ndgDthQv5v5BA==", + "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.3.0", @@ -10909,6 +11255,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">= 0.4" @@ -10918,6 +11265,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.6" @@ -11101,6 +11449,18 @@ "node": ">=8.10.0" } }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, "node_modules/tdigest": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz", @@ -11110,6 +11470,16 @@ "bintrees": "1.0.2" } }, + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -11171,18 +11541,21 @@ "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, "license": "MIT" }, "node_modules/tinyexec": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, "license": "MIT" }, "node_modules/tinyglobby": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.4.4", @@ -11199,6 +11572,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.0.tgz", "integrity": "sha512-7CotroY9a8DKsKprEy/a14aCCm8jYVmR7aFy4fpkZM8sdpNJbKkixuNjgM50yCmip2ezc8z4N7k3oe2+rfRJCQ==", + "dev": true, "license": "MIT", "engines": { "node": "^18.0.0 || >=20.0.0" @@ -11208,6 +11582,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, "license": "MIT", "engines": { "node": ">=14.0.0" @@ -11217,6 +11592,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz", "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==", + "dev": true, "license": "MIT", "engines": { "node": ">=14.0.0" @@ -11264,6 +11640,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -11625,6 +12002,7 @@ "version": "6.4.1", "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.25.0", @@ -11699,6 +12077,7 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.2.tgz", "integrity": "sha512-Xj/jovjZvDXOq2FgLXu8NsY4uHUMWtzVmMC2LkCu9HWdr9Qu1Is5sanX3Z4jOFKdohfaWDnEJWp9pRP0vVpAcA==", + "dev": true, "license": "MIT", "dependencies": { "cac": "^6.7.14", @@ -11721,12 +12100,14 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, "license": "MIT" }, "node_modules/vite/node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -11741,7 +12122,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.0.6.tgz", "integrity": "sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA==", - "devOptional": true, + "dev": true, "license": "MIT", "workspaces": [ "tests/deps/*", @@ -11760,6 +12141,7 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.2.tgz", "integrity": "sha512-fyNn/Rp016Bt5qvY0OQvIUCwW2vnaEBLxP42PmKbNIoasSYjML+8xyeADOPvBe+Xfl/ubIw4og7Lt9jflRsCNw==", + "dev": true, "license": "MIT", "dependencies": { "@types/chai": "^5.2.2", @@ -11849,6 +12231,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, "license": "MIT" }, "node_modules/w3c-xmlserializer": { @@ -11941,6 +12324,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, "license": "MIT", "dependencies": { "siginfo": "^2.0.0", @@ -12132,6 +12516,20 @@ "node": ">= 14.6" } }, + "node_modules/yauzl": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.2.0.tgz", + "integrity": "sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "pend": "~1.2.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/yazl": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/yazl/-/yazl-3.3.1.tgz", @@ -12175,6 +12573,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", + "dev": true, "license": "MIT" }, "node_modules/zod": { diff --git a/frontend/package.json b/frontend/package.json index d8a45fa47..c2549c78b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -46,6 +46,7 @@ "isomorphic-dompurify": "2.13.0", "js-yaml": "^4.1.0", "minimist": "^1.2.8", + "mongodb-memory-server": "^11.0.1", "playwright": "^1.55.1", "prettier": "^3.5.3", "prettier-plugin-svelte": "^3.2.6", diff --git a/frontend/src/lib/components/SignInButton.svelte b/frontend/src/lib/components/SignInButton.svelte new file mode 100644 index 000000000..874b81619 --- /dev/null +++ b/frontend/src/lib/components/SignInButton.svelte @@ -0,0 +1,52 @@ + + + diff --git a/frontend/src/lib/server/auth.spec.ts b/frontend/src/lib/server/auth.spec.ts new file mode 100644 index 000000000..5bc9aad66 --- /dev/null +++ b/frontend/src/lib/server/auth.spec.ts @@ -0,0 +1,234 @@ +/** + * Unit tests for OAuth provider error handling in auth.ts + * + * Runs in the "server" Vitest workspace (environment: node). + * Uses vi.hoisted() so that mock variables are accessible inside vi.mock() factories. + */ +import { describe, it, expect, vi, beforeEach } from "vitest"; + +// --------------------------------------------------------------------------- +// Hoist mock references so they can be used in vi.mock() factories +// --------------------------------------------------------------------------- +const { mockIssuerDiscover } = vi.hoisted(() => ({ + mockIssuerDiscover: vi.fn(), +})); + +// --------------------------------------------------------------------------- +// All vi.mock() calls – must appear before any named imports +// --------------------------------------------------------------------------- +vi.mock("$app/paths", () => ({ base: "" })); +vi.mock("$app/environment", () => ({ dev: false })); +vi.mock("$lib/migrations/lock", () => ({ + acquireLock: vi.fn(), + releaseLock: vi.fn(), + isDBLocked: vi.fn(), +})); +vi.mock("$lib/utils/sha256", () => ({ + sha256: vi.fn(async (v: string) => v + "-hashed"), +})); +vi.mock("bson-objectid", () => ({ default: class ObjectId { } })); +vi.mock("$lib/types/Semaphore", () => ({ Semaphores: { OAUTH_TOKEN_REFRESH: "oauth_refresh" } })); +vi.mock("$lib/server/database", () => ({ collections: {} })); +vi.mock("./adminToken", () => ({ + adminTokenManager: { isAdmin: vi.fn(() => false) }, +})); +vi.mock("$lib/server/logger", () => ({ + logger: { error: vi.fn(), info: vi.fn(), warn: vi.fn() }, +})); +vi.mock("$lib/server/config", () => ({ + config: { + OPENID_CLIENT_ID: "test-client-id", + OPENID_CLIENT_SECRET: "test-client-secret", + OPENID_PROVIDER_URL: "https://provider.example.com", + OPENID_SCOPES: "openid profile", + OPENID_NAME_CLAIM: "name", + OPENID_TOLERANCE: "", + OPENID_RESOURCE: "", + OPENID_CONFIG: "{}", + COOKIE_NAME: "hf-chat", + COOKIE_SAMESITE: "", + COOKIE_SECURE: "", + ALLOW_INSECURE_COOKIES: "true", + PUBLIC_ORIGIN: "http://localhost:5173", + COUPLE_SESSION_WITH_COOKIE_NAME: "", + TRUSTED_EMAIL_HEADER: "", + ALTERNATIVE_REDIRECT_URLS: [], + }, +})); +vi.mock("openid-client", () => ({ + Issuer: { discover: mockIssuerDiscover }, + custom: { clock_tolerance: Symbol("clock_tolerance") }, + generators: { + codeVerifier: vi.fn(() => "test-code-verifier"), + codeChallenge: vi.fn(() => "test-code-challenge"), + }, +})); + +// --------------------------------------------------------------------------- +// Imports (after all vi.mock() calls) +// --------------------------------------------------------------------------- +import { OAuthProviderError } from "./auth"; + +// --------------------------------------------------------------------------- +// OAuthProviderError class tests +// --------------------------------------------------------------------------- +describe("OAuthProviderError", () => { + it("defaults code to OAUTH_PROVIDER_ERROR", () => { + const err = new OAuthProviderError("Something went wrong"); + expect(err.code).toBe("OAUTH_PROVIDER_ERROR"); + expect(err.name).toBe("OAuthProviderError"); + expect(err.message).toBe("Something went wrong"); + }); + + it("accepts OAUTH_PROVIDER_UNAVAILABLE code", () => { + const err = new OAuthProviderError("Provider down", "OAUTH_PROVIDER_UNAVAILABLE"); + expect(err.code).toBe("OAUTH_PROVIDER_UNAVAILABLE"); + }); + + it("is an instance of Error", () => { + const err = new OAuthProviderError("test"); + expect(err instanceof Error).toBe(true); + }); +}); + +// --------------------------------------------------------------------------- +// triggerOauthFlow error handling tests +// --------------------------------------------------------------------------- +describe("triggerOauthFlow – provider failures", () => { + const makeEvent = () => ({ + url: new URL("http://localhost:5173/login"), + locals: { sessionId: "test-session-id" }, + cookies: { + set: vi.fn(), + get: vi.fn(), + getAll: vi.fn(() => []), + delete: vi.fn(), + serialize: vi.fn(), + }, + request: new Request("http://localhost:5173/login"), + }); + + beforeEach(() => { + // Reset module cache to clear auth.ts's module-level lastIssuer cache. + vi.resetModules(); + vi.clearAllMocks(); + mockIssuerDiscover.mockReset(); + }); + + it("returns 502 + OAUTH_PROVIDER_UNAVAILABLE on TypeError (fetch failed)", async () => { + mockIssuerDiscover.mockRejectedValue(new TypeError("fetch failed")); + const { triggerOauthFlow } = await import("./auth"); + const response = await triggerOauthFlow(makeEvent() as never); + + expect(response.status).toBe(502); + const body = await response.json(); + expect(body.code).toBe("OAUTH_PROVIDER_UNAVAILABLE"); + expect(typeof body.message).toBe("string"); + }); + + it("returns 502 + OAUTH_PROVIDER_UNAVAILABLE on ECONNREFUSED", async () => { + mockIssuerDiscover.mockRejectedValue(new Error("connect ECONNREFUSED 127.0.0.1:443")); + const { triggerOauthFlow } = await import("./auth"); + const response = await triggerOauthFlow(makeEvent() as never); + + expect(response.status).toBe(502); + const body = await response.json(); + expect(body.code).toBe("OAUTH_PROVIDER_UNAVAILABLE"); + }); + + it("returns 502 + OAUTH_PROVIDER_UNAVAILABLE on ENOTFOUND (DNS failure)", async () => { + mockIssuerDiscover.mockRejectedValue( + new Error("getaddrinfo ENOTFOUND provider.example.com") + ); + const { triggerOauthFlow } = await import("./auth"); + const response = await triggerOauthFlow(makeEvent() as never); + + expect(response.status).toBe(502); + const body = await response.json(); + expect(body.code).toBe("OAUTH_PROVIDER_UNAVAILABLE"); + }); + + it("returns 502 + OAUTH_PROVIDER_ERROR on non-network provider error", async () => { + mockIssuerDiscover.mockRejectedValue(new Error("Invalid discovery document")); + const { triggerOauthFlow } = await import("./auth"); + const response = await triggerOauthFlow(makeEvent() as never); + + expect(response.status).toBe(502); + const body = await response.json(); + expect(body.code).toBe("OAUTH_PROVIDER_ERROR"); + }); + + it("has Content-Type application/json on error response", async () => { + mockIssuerDiscover.mockRejectedValue(new Error("provider error")); + const { triggerOauthFlow } = await import("./auth"); + const response = await triggerOauthFlow(makeEvent() as never); + + expect(response.headers.get("Content-Type")).toContain("application/json"); + }); + + it("lets SvelteKit redirect pass through when provider is healthy", async () => { + mockIssuerDiscover.mockResolvedValue({ + metadata: { id_token_signing_alg_values_supported: ["RS256"] }, + Client: class { + authorizationUrl() { + return "https://provider.example.com/auth?code_challenge=xyz"; + } + }, + }); + + const { triggerOauthFlow } = await import("./auth"); + await expect(triggerOauthFlow(makeEvent() as never)).rejects.toMatchObject({ status: 302 }); + }); +}); + +// --------------------------------------------------------------------------- +// getOIDCUserData error handling tests +// --------------------------------------------------------------------------- +describe("getOIDCUserData – provider failures", () => { + beforeEach(() => { + // Reset module cache to clear auth.ts's module-level lastIssuer cache. + vi.resetModules(); + vi.clearAllMocks(); + mockIssuerDiscover.mockReset(); + }); + + it("throws OAuthProviderError OAUTH_PROVIDER_UNAVAILABLE on TypeError fetch failed", async () => { + mockIssuerDiscover.mockRejectedValue(new TypeError("fetch failed")); + const { getOIDCUserData } = await import("./auth"); + + await expect( + getOIDCUserData( + { redirectURI: "http://localhost:5173/login/callback" }, + "test-code", + "test-verifier", + undefined, + new URL("http://localhost:5173") + ) + ).rejects.toSatisfy( + (e: any) => e.name === "OAuthProviderError" && e.code === "OAUTH_PROVIDER_UNAVAILABLE" + ); + }); + + it("throws OAuthProviderError OAUTH_PROVIDER_ERROR on token exchange error", async () => { + mockIssuerDiscover.mockResolvedValue({ + metadata: { id_token_signing_alg_values_supported: ["RS256"] }, + Client: class { + callback = vi.fn().mockRejectedValue(new Error("invalid_grant")); + userinfo = vi.fn(); + }, + }); + const { getOIDCUserData } = await import("./auth"); + + await expect( + getOIDCUserData( + { redirectURI: "http://localhost:5173/login/callback" }, + "test-code", + "test-verifier", + undefined, + new URL("http://localhost:5173") + ) + ).rejects.toSatisfy( + (e: any) => e.name === "OAuthProviderError" && e.code === "OAUTH_PROVIDER_ERROR" + ); + }); +}); diff --git a/frontend/src/lib/server/auth.ts b/frontend/src/lib/server/auth.ts index 5f9f299af..c3e3a35a3 100644 --- a/frontend/src/lib/server/auth.ts +++ b/frontend/src/lib/server/auth.ts @@ -33,6 +33,20 @@ export interface OIDCUserInfo { userData: UserinfoResponse; } +/** Structured error codes returned when an OAuth provider is unavailable or misbehaving. */ +export type OAuthErrorCode = "OAUTH_PROVIDER_UNAVAILABLE" | "OAUTH_PROVIDER_ERROR"; + +/** Thrown (and caught) to distinguish OAuth provider failures from other errors. */ +export class OAuthProviderError extends Error { + public readonly code: OAuthErrorCode; + + constructor(message: string, code: OAuthErrorCode = "OAUTH_PROVIDER_ERROR") { + super(message); + this.name = "OAuthProviderError"; + this.code = code; + } +} + const stringWithDefault = (value: string) => z .string() @@ -319,18 +333,33 @@ export async function getOIDCUserData( iss: string | undefined, url: URL ): Promise { - const client = await getOIDCClient(settings, url); - const token = await client.callback( - settings.redirectURI, - { - code, - iss, - }, - { code_verifier: codeVerifier } - ); - const userData = await client.userinfo(token); - - return { token, userData }; + try { + const client = await getOIDCClient(settings, url); + const token = await client.callback( + settings.redirectURI, + { + code, + iss, + }, + { code_verifier: codeVerifier } + ); + const userData = await client.userinfo(token); + return { token, userData }; + } catch (err) { + // Re-wrap as a typed error so callers (e.g. callback +server.ts) can distinguish + // provider unavailability from other failures without logging sensitive token data. + const isNetworkError = + err instanceof TypeError || + (err instanceof Error && (err.message.includes("ECONNREFUSED") || err.message.includes("ENOTFOUND") || err.message.includes("fetch failed"))); + const code: OAuthErrorCode = isNetworkError + ? "OAUTH_PROVIDER_UNAVAILABLE" + : "OAUTH_PROVIDER_ERROR"; + logger.error({ code, msg: err instanceof Error ? err.message : String(err) }, "OAuth provider error in getOIDCUserData"); + throw new OAuthProviderError( + isNetworkError ? "OAuth provider is unreachable" : "OAuth provider returned an error", + code + ); + } } /** @@ -522,8 +551,6 @@ export async function triggerOauthFlow({ url, locals, cookies }: RequestEvent): // let redirectURI = `${(referer ? new URL(referer) : url).origin}${base}/login/callback`; let redirectURI = `${url.origin}${base}/login/callback`; - // TODO: Handle errors if provider is not responding - if (url.searchParams.has("callback")) { const callback = url.searchParams.get("callback") || redirectURI; if (config.ALTERNATIVE_REDIRECT_URLS.includes(callback)) { @@ -545,10 +572,47 @@ export async function triggerOauthFlow({ url, locals, cookies }: RequestEvent): next = sanitizeReturnPath(`${base}/`) ?? "/"; } - const authorizationUrl = await getOIDCAuthorizationUrl( - { redirectURI }, - { sessionId: locals.sessionId, next, url, cookies } - ); + try { + const authorizationUrl = await getOIDCAuthorizationUrl( + { redirectURI }, + { sessionId: locals.sessionId, next, url, cookies } + ); + throw redirect(302, authorizationUrl); + } catch (err) { + // Let SvelteKit redirect responses pass through unchanged. + if (err instanceof Response || (err && typeof err === "object" && "status" in err && "location" in err)) { + throw err; + } - throw redirect(302, authorizationUrl); + // Determine structured error code based on error type. + const isNetworkError = + err instanceof TypeError || + (err instanceof Error && + (err.message.includes("ECONNREFUSED") || + err.message.includes("ENOTFOUND") || + err.message.includes("fetch failed"))); + const errorCode: OAuthErrorCode = isNetworkError + ? "OAUTH_PROVIDER_UNAVAILABLE" + : "OAUTH_PROVIDER_ERROR"; + + // Log the error without sensitive data (no tokens, no secrets). + logger.error( + { code: errorCode, msg: err instanceof Error ? err.message : String(err) }, + "OAuth provider error during authorization URL generation" + ); + + return new Response( + JSON.stringify({ + code: errorCode, + message: + errorCode === "OAUTH_PROVIDER_UNAVAILABLE" + ? "The sign-in service is temporarily unavailable. Please try again later." + : "An error occurred during sign-in. Please try again.", + }), + { + status: 502, + headers: { "Content-Type": "application/json" }, + } + ); + } } diff --git a/frontend/src/routes/login/callback/+server.ts b/frontend/src/routes/login/callback/+server.ts index 9e04ae8a3..7a81583ac 100644 --- a/frontend/src/routes/login/callback/+server.ts +++ b/frontend/src/routes/login/callback/+server.ts @@ -1,10 +1,11 @@ import { error, redirect } from "@sveltejs/kit"; -import { getOIDCUserData, validateAndParseCsrfToken } from "$lib/server/auth"; +import { getOIDCUserData, validateAndParseCsrfToken, OAuthProviderError } from "$lib/server/auth"; import { z } from "zod"; import { base } from "$app/paths"; import { config } from "$lib/server/config"; import JSON5 from "json5"; import { updateUser } from "./updateUser.js"; +import { logger } from "$lib/server/logger"; const sanitizeJSONEnv = (val: string, fallback: string) => { const raw = (val ?? "").trim(); @@ -57,13 +58,31 @@ export async function GET({ url, locals, cookies, request, getClientAddress }) { throw error(403, "Code verifier cookie not found"); } - const { userData, token } = await getOIDCUserData( - { redirectURI: validatedToken.redirectUrl }, - code, - codeVerifier, - iss, - url - ); + let userData: Awaited>["userData"]; + let token: Awaited>["token"]; + try { + ({ userData, token } = await getOIDCUserData( + { redirectURI: validatedToken.redirectUrl }, + code, + codeVerifier, + iss, + url + )); + } catch (err) { + if (err instanceof OAuthProviderError) { + logger.error( + { code: err.code, msg: err.message }, + "OAuth provider error during callback token exchange" + ); + throw error( + 502, + err.code === "OAUTH_PROVIDER_UNAVAILABLE" + ? "The sign-in service is temporarily unavailable. Please try again later." + : "An error occurred during sign-in. Please try again." + ); + } + throw err; + } // Filter by allowed user emails or domains if (allowedUserEmails.length > 0 || allowedUserDomains.length > 0) { diff --git a/frontend/src/routes/login/callback/auth-provider-failure.spec.ts b/frontend/src/routes/login/callback/auth-provider-failure.spec.ts new file mode 100644 index 000000000..309eb70ca --- /dev/null +++ b/frontend/src/routes/login/callback/auth-provider-failure.spec.ts @@ -0,0 +1,170 @@ +/** + * Integration tests for OAuth provider failure in the login callback flow. + * + * Runs in the "server" Vitest workspace (environment: node). + * Uses vi.hoisted() so that mock variables are accessible inside vi.mock() factories. + */ +import { describe, it, expect, vi, beforeEach } from "vitest"; + +// --------------------------------------------------------------------------- +// Hoist mock references so they can be used in vi.mock() factories +// --------------------------------------------------------------------------- +const { mockIssuerDiscover } = vi.hoisted(() => ({ + mockIssuerDiscover: vi.fn(), +})); + +// --------------------------------------------------------------------------- +// All vi.mock() calls – must appear before any named imports +// --------------------------------------------------------------------------- +vi.mock("$app/paths", () => ({ base: "" })); +vi.mock("$app/environment", () => ({ dev: false })); +vi.mock("$lib/server/database", () => ({ collections: {} })); +vi.mock("$lib/migrations/lock", () => ({ + acquireLock: vi.fn(), + releaseLock: vi.fn(), + isDBLocked: vi.fn(), +})); +vi.mock("$lib/utils/sha256", () => ({ + sha256: vi.fn(async (v: string) => v + "-hashed"), +})); +vi.mock("bson-objectid", () => ({ default: class ObjectId { } })); +vi.mock("$lib/types/Semaphore", () => ({ Semaphores: { OAUTH_TOKEN_REFRESH: "oauth_refresh" } })); +vi.mock("$lib/server/logger", () => ({ + logger: { error: vi.fn(), info: vi.fn(), warn: vi.fn() }, +})); +vi.mock("./adminToken", () => ({ + adminTokenManager: { isAdmin: vi.fn(() => false) }, +})); +vi.mock("$lib/server/config", () => ({ + config: { + OPENID_CLIENT_ID: "test-client-id", + OPENID_CLIENT_SECRET: "test-client-secret", + OPENID_PROVIDER_URL: "https://provider.example.com", + OPENID_SCOPES: "openid profile", + OPENID_NAME_CLAIM: "name", + OPENID_TOLERANCE: "", + OPENID_RESOURCE: "", + OPENID_CONFIG: "{}", + COOKIE_NAME: "hf-chat", + COOKIE_SAMESITE: "", + COOKIE_SECURE: "", + ALLOW_INSECURE_COOKIES: "true", + PUBLIC_ORIGIN: "http://localhost:5173", + COUPLE_SESSION_WITH_COOKIE_NAME: "", + TRUSTED_EMAIL_HEADER: "", + ALTERNATIVE_REDIRECT_URLS: [], + }, +})); +vi.mock("openid-client", () => ({ + Issuer: { discover: mockIssuerDiscover }, + custom: { clock_tolerance: Symbol("clock_tolerance") }, + generators: { + codeVerifier: vi.fn(() => "test-code-verifier"), + codeChallenge: vi.fn(() => "test-code-challenge"), + }, +})); + +// --------------------------------------------------------------------------- +// Imports (after all vi.mock() calls) +// --------------------------------------------------------------------------- +import { OAuthProviderError } from "$lib/server/auth"; + +// --------------------------------------------------------------------------- +// Helper to call triggerOauthFlow with a mocked-down provider +// --------------------------------------------------------------------------- +async function simulateProviderDown( + providerError: Error +): Promise<{ status: number; body: { code: string; message: string } }> { + mockIssuerDiscover.mockRejectedValue(providerError); + const { triggerOauthFlow } = await import("$lib/server/auth"); + + const event = { + url: new URL("http://localhost:5173/login"), + locals: { sessionId: "e2e-session-id" }, + cookies: { + set: vi.fn(), + get: vi.fn(), + getAll: vi.fn(() => []), + delete: vi.fn(), + serialize: vi.fn(), + }, + request: new Request("http://localhost:5173/login"), + }; + + const response = await triggerOauthFlow(event as never); + const body = await response.json(); + return { status: response.status, body }; +} + +// --------------------------------------------------------------------------- +// E2E: provider unavailable scenarios +// --------------------------------------------------------------------------- +describe("E2E: OAuth provider unavailability", () => { + beforeEach(() => { + vi.clearAllMocks(); + mockIssuerDiscover.mockReset(); + }); + + it("returns 502 + OAUTH_PROVIDER_UNAVAILABLE when TypeError (fetch failed)", async () => { + const { status, body } = await simulateProviderDown(new TypeError("fetch failed")); + expect(status).toBe(502); + expect(body.code).toBe("OAUTH_PROVIDER_UNAVAILABLE"); + expect(body.message).toMatch(/temporarily unavailable/i); + }); + + it("returns 502 + OAUTH_PROVIDER_UNAVAILABLE when ECONNREFUSED", async () => { + const { status, body } = await simulateProviderDown( + new Error("connect ECONNREFUSED 127.0.0.1:443") + ); + expect(status).toBe(502); + expect(body.code).toBe("OAUTH_PROVIDER_UNAVAILABLE"); + }); + + it("returns 502 + OAUTH_PROVIDER_UNAVAILABLE when ENOTFOUND (DNS failure)", async () => { + const { status, body } = await simulateProviderDown( + new Error("getaddrinfo ENOTFOUND provider.example.com") + ); + expect(status).toBe(502); + expect(body.code).toBe("OAUTH_PROVIDER_UNAVAILABLE"); + }); + + it("returns 502 + OAUTH_PROVIDER_ERROR on non-network provider error", async () => { + const { status, body } = await simulateProviderDown( + new Error("Internal server error from OIDC provider") + ); + expect(status).toBe(502); + expect(body.code).toBe("OAUTH_PROVIDER_ERROR"); + expect(body.message).toMatch(/error occurred/i); + }); +}); + +// --------------------------------------------------------------------------- +// E2E: OAuthProviderError propagation +// --------------------------------------------------------------------------- +describe("E2E: OAuthProviderError propagation", () => { + it("carries OAUTH_PROVIDER_UNAVAILABLE code", () => { + const err = new OAuthProviderError("Provider unreachable", "OAUTH_PROVIDER_UNAVAILABLE"); + expect(err.name).toBe("OAuthProviderError"); + expect(err.code).toBe("OAUTH_PROVIDER_UNAVAILABLE"); + }); + + it("defaults to OAUTH_PROVIDER_ERROR code", () => { + const err = new OAuthProviderError("Unknown error"); + expect(err.code).toBe("OAUTH_PROVIDER_ERROR"); + }); + + it("non-OAuthProviderError errors are re-thrown unchanged", () => { + const originalErr = new Error("Some unrelated error"); + let rethrown: Error | null = null; + try { + throw originalErr; + } catch (e: any) { + if (e.name === "OAuthProviderError") { + rethrown = new Error("wrongly caught"); + } else { + rethrown = e as Error; + } + } + expect(rethrown).toBe(originalErr); + }); +}); From 2f5b5f5bff11c42ae00e2984cf29b8ae333e0a06 Mon Sep 17 00:00:00 2001 From: Suvam Paul Date: Mon, 2 Mar 2026 15:43:40 +0530 Subject: [PATCH 3/5] Update frontend/src/lib/components/SignInButton.svelte Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../src/lib/components/SignInButton.svelte | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/frontend/src/lib/components/SignInButton.svelte b/frontend/src/lib/components/SignInButton.svelte index 874b81619..0fabb69b0 100644 --- a/frontend/src/lib/components/SignInButton.svelte +++ b/frontend/src/lib/components/SignInButton.svelte @@ -37,8 +37,43 @@ } }); - function handleSignIn() { - window.location.href = `${base}/login`; + async function handleSignIn() { + try { + const response = await fetch(`${base}/login`); + + let data: any = null; + try { + data = await response.json(); + } catch { + // Response is not JSON; leave `data` as null. + } + + if (!response.ok) { + const errorCode = data?.error ?? data?.code; + if (errorCode && errorCode in OAUTH_ERROR_MESSAGES) { + errorStore.set(OAUTH_ERROR_MESSAGES[errorCode]); + } else if (data?.message && typeof data.message === "string") { + errorStore.set(data.message); + } else { + errorStore.set(OAUTH_ERROR_MESSAGES["OAUTH_PROVIDER_ERROR"]); + } + return; + } + + const redirectUrl = + (typeof data?.redirectUrl === "string" && data.redirectUrl) || + (typeof data?.url === "string" && data.url); + + if (redirectUrl) { + window.location.href = redirectUrl; + } else { + // Fallback to existing behavior if no URL is provided in the JSON. + window.location.href = `${base}/login`; + } + } catch { + // Network or unexpected error — treat as provider unavailable. + errorStore.set(OAUTH_ERROR_MESSAGES["OAUTH_PROVIDER_UNAVAILABLE"]); + } } From 0c7ff1db3bb558ee2b2aafa72198b76ffcbd1b22 Mon Sep 17 00:00:00 2001 From: Suvam Paul Date: Mon, 2 Mar 2026 15:43:51 +0530 Subject: [PATCH 4/5] Update frontend/src/lib/components/SignInButton.svelte Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../src/lib/components/SignInButton.svelte | 57 ++----------------- 1 file changed, 5 insertions(+), 52 deletions(-) diff --git a/frontend/src/lib/components/SignInButton.svelte b/frontend/src/lib/components/SignInButton.svelte index 0fabb69b0..8e9653dc8 100644 --- a/frontend/src/lib/components/SignInButton.svelte +++ b/frontend/src/lib/components/SignInButton.svelte @@ -33,55 +33,8 @@ errorStore.set(OAUTH_ERROR_MESSAGES[errorCode]); } else if (errorCode) { // Unknown error code — show a generic message - errorStore.set(OAUTH_ERROR_MESSAGES["OAUTH_PROVIDER_ERROR"]); - } - }); - - async function handleSignIn() { - try { - const response = await fetch(`${base}/login`); - - let data: any = null; - try { - data = await response.json(); - } catch { - // Response is not JSON; leave `data` as null. - } - - if (!response.ok) { - const errorCode = data?.error ?? data?.code; - if (errorCode && errorCode in OAUTH_ERROR_MESSAGES) { - errorStore.set(OAUTH_ERROR_MESSAGES[errorCode]); - } else if (data?.message && typeof data.message === "string") { - errorStore.set(data.message); - } else { - errorStore.set(OAUTH_ERROR_MESSAGES["OAUTH_PROVIDER_ERROR"]); - } - return; - } - - const redirectUrl = - (typeof data?.redirectUrl === "string" && data.redirectUrl) || - (typeof data?.url === "string" && data.url); - - if (redirectUrl) { - window.location.href = redirectUrl; - } else { - // Fallback to existing behavior if no URL is provided in the JSON. - window.location.href = `${base}/login`; - } - } catch { - // Network or unexpected error — treat as provider unavailable. - errorStore.set(OAUTH_ERROR_MESSAGES["OAUTH_PROVIDER_UNAVAILABLE"]); - } - } - - - + From 25d8b0a4fddc678a5489992cdec9e2b5df62acbf Mon Sep 17 00:00:00 2001 From: Suvam-paul145 Date: Fri, 27 Mar 2026 23:39:36 +0530 Subject: [PATCH 5/5] Oauth error handing done --- frontend/src/hooks.server.ts | 48 ++++++++- .../src/lib/components/SignInButton.svelte | 32 +++--- frontend/src/routes/+layout.svelte | 17 +++ frontend/src/routes/login/callback/+server.ts | 17 ++- .../callback/provider-error-response.spec.ts | 100 ++++++++++++++++++ 5 files changed, 189 insertions(+), 25 deletions(-) create mode 100644 frontend/src/routes/login/callback/provider-error-response.spec.ts diff --git a/frontend/src/hooks.server.ts b/frontend/src/hooks.server.ts index b16cd0426..d54dd3c3c 100644 --- a/frontend/src/hooks.server.ts +++ b/frontend/src/hooks.server.ts @@ -177,7 +177,29 @@ export const handle: Handle = async ({ event, resolve }) => { ) { // To get the same CSRF token after callback refreshSessionCookie(event.cookies, auth.secretSessionId); - return await triggerOauthFlow(event); + const oauthResponse = await triggerOauthFlow(event); + if (oauthResponse.status === 502) { + const accept = event.request.headers.get("accept") || ""; + const wantsHtml = accept.includes("text/html"); + + if (wantsHtml) { + try { + const body = await oauthResponse.clone().json(); + if (typeof body?.code === "string") { + return new Response(null, { + status: 302, + headers: { + Location: `${base}/?error=${encodeURIComponent(body.code)}`, + "Cache-Control": "no-store", + }, + }); + } + } catch { + // fall through and return original structured 502 response + } + } + } + return oauthResponse; } } else { // Redirect to OAuth flow unless on the authorized pages (home, shared conversation, login, healthcheck, model thumbnails) @@ -193,7 +215,29 @@ export const handle: Handle = async ({ event, resolve }) => { !event.url.pathname.startsWith(`${base}/api`) ) { refreshSessionCookie(event.cookies, auth.secretSessionId); - return triggerOauthFlow(event); + const oauthResponse = await triggerOauthFlow(event); + if (oauthResponse.status === 502) { + const accept = event.request.headers.get("accept") || ""; + const wantsHtml = accept.includes("text/html"); + + if (wantsHtml) { + try { + const body = await oauthResponse.clone().json(); + if (typeof body?.code === "string") { + return new Response(null, { + status: 302, + headers: { + Location: `${base}/?error=${encodeURIComponent(body.code)}`, + "Cache-Control": "no-store", + }, + }); + } + } catch { + // fall through and return original structured 502 response + } + } + } + return oauthResponse; } } } diff --git a/frontend/src/lib/components/SignInButton.svelte b/frontend/src/lib/components/SignInButton.svelte index 8e9653dc8..9bf7a0114 100644 --- a/frontend/src/lib/components/SignInButton.svelte +++ b/frontend/src/lib/components/SignInButton.svelte @@ -2,7 +2,7 @@ import { onMount } from "svelte"; import { base } from "$app/paths"; import { page } from "$app/state"; - import { error as errorStore } from "$lib/stores/errors"; + import { error as errorStore, ERROR_MESSAGES } from "$lib/stores/errors"; interface Props { /** Optional CSS classes to apply to the button element. */ @@ -13,28 +13,24 @@ let { classNames = "", label = "Sign in" }: Props = $props(); - /** - * Map server-returned OAuth error codes to human-readable messages shown in the toast. - * These codes are set by triggerOauthFlow / callback +server.ts when a provider fails. - */ const OAUTH_ERROR_MESSAGES: Record = { OAUTH_PROVIDER_UNAVAILABLE: "Sign-in service is temporarily unavailable. Please try again later.", OAUTH_PROVIDER_ERROR: "An error occurred during sign-in. Please try again.", }; - /** - * On mount, check for an `?error=` query param that the server sets when OAuth fails. - * Display the appropriate toast message via the shared error store. - */ onMount(() => { const errorCode = page.url.searchParams.get("error"); - if (errorCode && errorCode in OAUTH_ERROR_MESSAGES) { - errorStore.set(OAUTH_ERROR_MESSAGES[errorCode]); - } else if (errorCode) { - // Unknown error code — show a generic message - + if (!errorCode) return; + + errorStore.set(OAUTH_ERROR_MESSAGES[errorCode] ?? ERROR_MESSAGES.default); + }); + + function onSignIn() { + window.location.assign(`${base}/login`); + } + + + diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte index 06e9c69b4..7dc919ff4 100644 --- a/frontend/src/routes/+layout.svelte +++ b/frontend/src/routes/+layout.svelte @@ -135,6 +135,13 @@ let errorToastTimeout: ReturnType; let currentError: string | undefined = $state(); + let lastOAuthErrorCode: string | null = $state(null); + + const OAUTH_ERROR_MESSAGES: Record = { + OAUTH_PROVIDER_UNAVAILABLE: + "Sign-in service is temporarily unavailable. Please try again later.", + OAUTH_PROVIDER_ERROR: "An error occurred during sign-in. Please try again.", + }; async function onError() { // If a new different error comes, wait for the current error to hide first @@ -203,6 +210,16 @@ if ($error) onError(); }); + $effect(() => { + const oauthErrorCode = page.url.searchParams.get("error"); + if (!oauthErrorCode || oauthErrorCode === lastOAuthErrorCode) { + return; + } + + lastOAuthErrorCode = oauthErrorCode; + $error = OAUTH_ERROR_MESSAGES[oauthErrorCode] ?? ERROR_MESSAGES.default; + }); + $effect(() => { if ($titleUpdate) { const convIdx = conversations.findIndex(({ id }) => id === $titleUpdate?.convId); diff --git a/frontend/src/routes/login/callback/+server.ts b/frontend/src/routes/login/callback/+server.ts index 7a81583ac..838c7a51b 100644 --- a/frontend/src/routes/login/callback/+server.ts +++ b/frontend/src/routes/login/callback/+server.ts @@ -70,15 +70,22 @@ export async function GET({ url, locals, cookies, request, getClientAddress }) { )); } catch (err) { if (err instanceof OAuthProviderError) { + const message = + err.code === "OAUTH_PROVIDER_UNAVAILABLE" + ? "The sign-in service is temporarily unavailable. Please try again later." + : "An error occurred during sign-in. Please try again."; + logger.error( { code: err.code, msg: err.message }, "OAuth provider error during callback token exchange" ); - throw error( - 502, - err.code === "OAUTH_PROVIDER_UNAVAILABLE" - ? "The sign-in service is temporarily unavailable. Please try again later." - : "An error occurred during sign-in. Please try again." + + return new Response( + JSON.stringify({ code: err.code, message }), + { + status: 502, + headers: { "Content-Type": "application/json" }, + } ); } throw err; diff --git a/frontend/src/routes/login/callback/provider-error-response.spec.ts b/frontend/src/routes/login/callback/provider-error-response.spec.ts new file mode 100644 index 000000000..6076455b0 --- /dev/null +++ b/frontend/src/routes/login/callback/provider-error-response.spec.ts @@ -0,0 +1,100 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const { mockGetOIDCUserData, mockValidateAndParseCsrfToken, mockUpdateUser, MockOAuthProviderError } = + vi.hoisted(() => { + class MockOAuthProviderError extends Error { + public readonly code: "OAUTH_PROVIDER_UNAVAILABLE" | "OAUTH_PROVIDER_ERROR"; + constructor( + message: string, + code: "OAUTH_PROVIDER_UNAVAILABLE" | "OAUTH_PROVIDER_ERROR" = "OAUTH_PROVIDER_ERROR" + ) { + super(message); + this.name = "OAuthProviderError"; + this.code = code; + } + } + + return { + mockGetOIDCUserData: vi.fn(), + mockValidateAndParseCsrfToken: vi.fn(), + mockUpdateUser: vi.fn(), + MockOAuthProviderError, + }; + }); + +vi.mock("$app/paths", () => ({ base: "" })); +vi.mock("$lib/server/logger", () => ({ + logger: { error: vi.fn(), info: vi.fn(), warn: vi.fn() }, +})); +vi.mock("$lib/server/config", () => ({ + config: { + ALLOWED_USER_EMAILS: "[]", + ALLOWED_USER_DOMAINS: "[]", + }, +})); +vi.mock("$lib/server/auth", () => ({ + getOIDCUserData: mockGetOIDCUserData, + validateAndParseCsrfToken: mockValidateAndParseCsrfToken, + OAuthProviderError: MockOAuthProviderError, +})); +vi.mock("./updateUser.js", () => ({ + updateUser: mockUpdateUser, +})); + +const makeEvent = () => { + const state = Buffer.from("csrf-token").toString("base64"); + return { + url: new URL(`http://localhost:5173/login/callback?code=test-code&state=${encodeURIComponent(state)}`), + locals: { sessionId: "session-123" }, + cookies: { + get: vi.fn(() => "code-verifier"), + set: vi.fn(), + delete: vi.fn(), + getAll: vi.fn(() => []), + serialize: vi.fn(), + }, + request: new Request("http://localhost:5173/login/callback"), + getClientAddress: vi.fn(() => "127.0.0.1"), + }; +}; + +describe("login callback structured OAuth provider failures", () => { + beforeEach(() => { + vi.resetModules(); + vi.clearAllMocks(); + mockValidateAndParseCsrfToken.mockResolvedValue({ + redirectUrl: "http://localhost:5173/login/callback", + }); + }); + + it("returns 502 with OAUTH_PROVIDER_UNAVAILABLE JSON payload", async () => { + mockGetOIDCUserData.mockRejectedValue( + new MockOAuthProviderError("Provider unreachable", "OAUTH_PROVIDER_UNAVAILABLE") + ); + const { GET } = await import("./+server"); + + const response = await GET(makeEvent() as never); + const body = await response.json(); + + expect(response.status).toBe(502); + expect(response.headers.get("Content-Type")).toContain("application/json"); + expect(body.code).toBe("OAUTH_PROVIDER_UNAVAILABLE"); + expect(body.message).toMatch(/temporarily unavailable/i); + expect(mockUpdateUser).not.toHaveBeenCalled(); + }); + + it("returns 502 with OAUTH_PROVIDER_ERROR JSON payload", async () => { + mockGetOIDCUserData.mockRejectedValue( + new MockOAuthProviderError("Provider internal error", "OAUTH_PROVIDER_ERROR") + ); + const { GET } = await import("./+server"); + + const response = await GET(makeEvent() as never); + const body = await response.json(); + + expect(response.status).toBe(502); + expect(body.code).toBe("OAUTH_PROVIDER_ERROR"); + expect(body.message).toMatch(/error occurred during sign-in/i); + expect(mockUpdateUser).not.toHaveBeenCalled(); + }); +});