From 58c6a532568404772a34b83438e7fac2b5769223 Mon Sep 17 00:00:00 2001 From: Niv Date: Tue, 23 Dec 2025 13:50:55 +0200 Subject: [PATCH 01/16] update authzed sdk --- package.json | 2 +- yarn.lock | 622 +++++++++++++++++++++------------------------------ 2 files changed, 253 insertions(+), 371 deletions(-) diff --git a/package.json b/package.json index 7f84141..aecaea2 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ } }, "dependencies": { - "@authzed/authzed-node": "^1.5.1", + "@authzed/authzed-node": "^1.6.1", "lru-cache": "^10.4.3" } } diff --git a/yarn.lock b/yarn.lock index 8506c0f..7346e8e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,15 +10,15 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" -"@authzed/authzed-node@^1.5.1": - version "1.5.1" - resolved "https://registry.npmjs.org/@authzed/authzed-node/-/authzed-node-1.5.1.tgz" - integrity sha512-Q2a3uIDoqi6JA7Ha4YSCz/YhOGojZjluskNV1HcSYszePFSo6uWc9tCCLAqR88VDplEYXsCJocUYVNNclmDQqg== +"@authzed/authzed-node@^1.6.1": + version "1.6.1" + resolved "https://registry.yarnpkg.com/@authzed/authzed-node/-/authzed-node-1.6.1.tgz#b7e4930bd6ccf4499092a54e094f7104a80fc628" + integrity sha512-Rj3rMtWOjo3igxY/2fpPrIedCTfDq3e+weykuxNBzV/y6azBCoXp8SzpjCEJXVcWyBD8bu/EKY3cye3kOLsKpQ== dependencies: "@grpc/grpc-js" "^1.13.4" - "@protobuf-ts/runtime" "^2.11.0" - "@protobuf-ts/runtime-rpc" "^2.11.0" - google-protobuf "^3.21.4" + "@protobuf-ts/runtime" "^2.11.1" + "@protobuf-ts/runtime-rpc" "^2.11.1" + google-protobuf "^4.0.0" "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.21.4", "@babel/code-frame@^7.23.5", "@babel/code-frame@^7.24.2": version "7.24.2" @@ -396,7 +396,7 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@^9.2.0", "@eslint/js@9.2.0": +"@eslint/js@9.2.0", "@eslint/js@^9.2.0": version "9.2.0" resolved "https://registry.npmjs.org/@eslint/js/-/js-9.2.0.tgz" integrity sha512-ESiIudvhoYni+MdsI8oD7skpprZ89qKocwRM2KEvhhBJ9nl5MRh7BXU5GTod7Mdygq+AUl+QzId6iWJKR/wABA== @@ -697,14 +697,6 @@ resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": - version "0.3.25" - resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz" - integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - "@jridgewell/trace-mapping@0.3.9": version "0.3.9" resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz" @@ -713,6 +705,14 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + "@js-sdsl/ordered-map@^4.4.2": version "4.4.2" resolved "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz" @@ -726,7 +726,7 @@ "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": version "2.0.5" resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== @@ -793,7 +793,9 @@ walk-up-path "^3.0.1" "@npmcli/disparity-colors@^3.0.0": - version "3.0.0" + version "3.0.1" + resolved "https://registry.yarnpkg.com/@npmcli/disparity-colors/-/disparity-colors-3.0.1.tgz#042d5ef548200c81e3ee3a84c994744573fe79fd" + integrity sha512-cOypTz/9IAhaPgOktbDNPeccTU88y8I1ZURbPeC0ooziK1h6dRJs2iGz1eKP1muaeVbow8GqQ0DaxLG8Bpmblw== dependencies: ansi-styles "^4.3.0" @@ -827,13 +829,17 @@ which "^3.0.0" "@npmcli/installed-package-contents@^2.0.1", "@npmcli/installed-package-contents@^2.0.2": - version "2.0.2" + version "2.1.0" + resolved "https://registry.yarnpkg.com/@npmcli/installed-package-contents/-/installed-package-contents-2.1.0.tgz#63048e5f6e40947a3a88dcbcb4fd9b76fdd37c17" + integrity sha512-c8UuGLeZpm69BryRykLuKRyKFZYJsZSCT4aVY5ds4omyZqJ172ApzgfKJ5eV/r3HgLdUYgFVe54KSFVjKoe27w== dependencies: npm-bundled "^3.0.0" npm-normalize-package-bin "^3.0.0" "@npmcli/map-workspaces@^3.0.2", "@npmcli/map-workspaces@^3.0.4": - version "3.0.4" + version "3.0.6" + resolved "https://registry.yarnpkg.com/@npmcli/map-workspaces/-/map-workspaces-3.0.6.tgz#27dc06c20c35ef01e45a08909cab9cb3da08cea6" + integrity sha512-tkYs0OYnzQm6iIRdfy+LcLBjcKuQCeE5YLb8KnrIlutJfheNaPvPpgoFEyEFgbjzl5PLZ3IA/BWAwRU0eHuQDA== dependencies: "@npmcli/name-from-folder" "^2.0.0" glob "^10.2.2" @@ -994,21 +1000,7 @@ "@octokit/types" "^13.1.0" universal-user-agent "^6.0.0" -"@octokit/types@^12.0.0": - version "12.6.0" - resolved "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz" - integrity sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw== - dependencies: - "@octokit/openapi-types" "^20.0.0" - -"@octokit/types@^12.2.0": - version "12.6.0" - resolved "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz" - integrity sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw== - dependencies: - "@octokit/openapi-types" "^20.0.0" - -"@octokit/types@^12.6.0": +"@octokit/types@^12.0.0", "@octokit/types@^12.2.0", "@octokit/types@^12.6.0": version "12.6.0" resolved "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz" integrity sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw== @@ -1053,14 +1045,14 @@ "@pnpm/network.ca-file" "^1.0.1" config-chain "^1.1.11" -"@protobuf-ts/runtime-rpc@^2.11.0": +"@protobuf-ts/runtime-rpc@^2.11.1": version "2.11.1" - resolved "https://registry.npmjs.org/@protobuf-ts/runtime-rpc/-/runtime-rpc-2.11.1.tgz" + resolved "https://registry.yarnpkg.com/@protobuf-ts/runtime-rpc/-/runtime-rpc-2.11.1.tgz#a6eb2f384bceae8d23a01d0b0e37faf0af36c179" integrity sha512-4CqqUmNA+/uMz00+d3CYKgElXO9VrEbucjnBFEjqI4GuDrEQ32MaI3q+9qPBvIGOlL4PmHXrzM32vBPWRhQKWQ== dependencies: "@protobuf-ts/runtime" "^2.11.1" -"@protobuf-ts/runtime@^2.11.0", "@protobuf-ts/runtime@^2.11.1": +"@protobuf-ts/runtime@^2.11.1": version "2.11.1" resolved "https://registry.npmjs.org/@protobuf-ts/runtime/-/runtime-2.11.1.tgz" integrity sha512-KuDaT1IfHkugM2pyz+FwiY80ejWrkH1pAtOBOZFuR6SXEFTsnb/jiQWQ1rCIrcKx2BtyxnxW6BWwsVSA/Ie+WQ== @@ -1443,7 +1435,7 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^7.8.0", "@typescript-eslint/eslint-plugin@7.8.0": +"@typescript-eslint/eslint-plugin@7.8.0", "@typescript-eslint/eslint-plugin@^7.8.0": version "7.8.0" resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.8.0.tgz" integrity sha512-gFTT+ezJmkwutUPmB0skOj3GZJtlEGnlssems4AjkVweUPGj7jRwwqg0Hhg7++kPGJqKtTYx+R05Ftww372aIg== @@ -1460,7 +1452,7 @@ semver "^7.6.0" ts-api-utils "^1.3.0" -"@typescript-eslint/parser@^7.8.0", "@typescript-eslint/parser@7.8.0": +"@typescript-eslint/parser@7.8.0", "@typescript-eslint/parser@^7.8.0": version "7.8.0" resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.8.0.tgz" integrity sha512-KgKQly1pv0l4ltcftP59uQZCi4HUYswCLbTqVZEJu7uLX8CTLyswqMLqLN+2QFz4jCptqWVV4SB7vdxcH2+0kQ== @@ -1529,6 +1521,14 @@ "@typescript-eslint/types" "7.8.0" eslint-visitor-keys "^3.4.3" +JSONStream@^1.3.5: + version "1.3.5" + resolved "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz" + integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== + dependencies: + jsonparse "^1.2.0" + through ">=2.2.7 <3" + abbrev@^1.0.0: version "1.1.1" resolved "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz" @@ -1554,7 +1554,7 @@ acorn@^8.11.3, acorn@^8.4.1: resolved "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz" integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== -agent-base@^6.0.2, agent-base@6: +agent-base@6, agent-base@^6.0.2: version "6.0.2" resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz" integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== @@ -1898,7 +1898,9 @@ before-after-hook@^2.2.0: integrity sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ== bin-links@^4.0.1: - version "4.0.3" + version "4.0.4" + resolved "https://registry.yarnpkg.com/bin-links/-/bin-links-4.0.4.tgz#c3565832b8e287c85f109a02a17027d152a58a63" + integrity sha512-cMtq4W5ZsEwcutJrVId+a/tjt8GSbS+h0oNkdl6+6rBuEv8Ot33Bevj5KPm40t309zuhVic8NjpuL42QCiJWWA== dependencies: cmd-shim "^6.0.0" npm-normalize-package-bin "^3.0.0" @@ -1906,7 +1908,9 @@ bin-links@^4.0.1: write-file-atomic "^5.0.0" binary-extensions@^2.2.0: - version "2.2.0" + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== bl@^4.1.0: version "4.1.0" @@ -1982,7 +1986,9 @@ buffer@^5.5.0: ieee754 "^1.1.13" builtins@^5.0.0: - version "5.0.1" + version "5.1.0" + resolved "https://registry.yarnpkg.com/builtins/-/builtins-5.1.0.tgz#6d85eeb360c4ebc166c3fdef922a15aa7316a5e8" + integrity sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg== dependencies: semver "^7.0.0" @@ -2081,25 +2087,7 @@ cardinal@^2.1.1: ansicolors "~0.3.2" redeyed "~2.1.0" -chalk@^2.3.2: - version "2.4.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^2.4.1: - version "2.4.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^2.4.2: +chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -2116,12 +2104,7 @@ chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^5.2.0: - version "5.3.0" - resolved "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz" - integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== - -chalk@^5.3.0: +chalk@^5.2.0, chalk@^5.3.0: version "5.3.0" resolved "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz" integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== @@ -2224,7 +2207,9 @@ clone@^1.0.2: integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== cmd-shim@^6.0.0: - version "6.0.2" + version "6.0.3" + resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-6.0.3.tgz#c491e9656594ba17ac83c4bd931590a9d6e26033" + integrity sha512-FMabTRlc5t5zjdenF6mS0MBeFZm0XqHqeOkcskKFb/LYCcRQ5fVgLOHVc4Lq9CqABd9zhjwPjMBCJvMCziSVtA== co@^4.6.0: version "4.6.0" @@ -2250,16 +2235,16 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - color-name@1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + color-support@^1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz" @@ -2374,8 +2359,8 @@ conventional-commits-parser@^5.0.0: resolved "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz" integrity sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA== dependencies: - is-text-path "^2.0.0" JSONStream "^1.3.5" + is-text-path "^2.0.0" meow "^12.0.1" split2 "^4.0.0" @@ -2434,16 +2419,7 @@ create-require@^1.1.0: resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== -cross-spawn@^7.0.0: - version "7.0.3" - resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -cross-spawn@^7.0.2, cross-spawn@^7.0.3: +cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -2464,7 +2440,7 @@ cssesc@^3.0.0: resolved "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== -cz-conventional-changelog@^3.3.0, cz-conventional-changelog@3.3.0: +cz-conventional-changelog@3.3.0, cz-conventional-changelog@^3.3.0: version "3.3.0" resolved "https://registry.npmjs.org/cz-conventional-changelog/-/cz-conventional-changelog-3.3.0.tgz" integrity sha512-U466fIzU5U22eES5lTNiNbZ+d8dfcHcssH4o7QsdWaCcRs/feIPCxKYSWkYBNs5mny7MvEfwpTLWjvbm94hecw== @@ -2510,6 +2486,13 @@ dateformat@^3.0.3: resolved "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== +debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + debug@^3.2.7: version "3.2.7" resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz" @@ -2517,13 +2500,6 @@ debug@^3.2.7: dependencies: ms "^2.1.1" -debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@4: - version "4.3.4" - resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - decamelize-keys@^1.1.0: version "1.1.1" resolved "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz" @@ -2537,16 +2513,16 @@ decamelize@^1.1.0: resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== -dedent@^1.0.0: - version "1.5.3" - resolved "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz" - integrity sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ== - dedent@0.7.0: version "0.7.0" resolved "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz" integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== +dedent@^1.0.0: + version "1.5.3" + resolved "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz" + integrity sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ== + deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz" @@ -2708,12 +2684,7 @@ env-ci@^9.0.0: execa "^7.0.0" java-properties "^1.0.2" -env-paths@^2.2.0: - version "2.2.1" - resolved "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz" - integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== - -env-paths@^2.2.1: +env-paths@^2.2.0, env-paths@^2.2.1: version "2.2.1" resolved "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz" integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== @@ -2831,6 +2802,11 @@ escalade@^3.1.1, escalade@^3.1.2: resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz" integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== +escape-string-regexp@5.0.0, escape-string-regexp@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz" + integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== + escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" @@ -2846,16 +2822,6 @@ escape-string-regexp@^4.0.0: resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -escape-string-regexp@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz" - integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== - -escape-string-regexp@5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz" - integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== - eslint-config-prettier@^9.1.0: version "9.1.0" resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz" @@ -3107,7 +3073,7 @@ fast-glob@^3.2.9, fast-glob@^3.3.2: merge2 "^1.3.0" micromatch "^4.0.4" -fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0, fast-json-stable-stringify@2.x: +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -3283,15 +3249,6 @@ from2@^2.3.0: inherits "^2.0.1" readable-stream "^2.0.0" -fs-extra@^11.0.0: - version "11.2.0" - resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz" - integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - fs-extra@9.1.0: version "9.1.0" resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz" @@ -3302,14 +3259,16 @@ fs-extra@9.1.0: jsonfile "^6.0.1" universalify "^2.0.0" -fs-minipass@^2.0.0: - version "2.1.0" - resolved "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz" - integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== +fs-extra@^11.0.0: + version "11.2.0" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz" + integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw== dependencies: - minipass "^3.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" -fs-minipass@^2.1.0: +fs-minipass@^2.0.0, fs-minipass@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz" integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== @@ -3368,7 +3327,9 @@ gauge@^4.0.3: wide-align "^1.1.5" gauge@^5.0.0: - version "5.0.1" + version "5.0.2" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-5.0.2.tgz#7ab44c11181da9766333f10db8cd1e4b17fd6c46" + integrity sha512-pMaFftXPtiGIHCJHdcUUx9Rby/rFT/Kkt3fIIGCs+9PMDIljSyRiqraTlxNtBReJRDfUefpa263RQ3vnp5G/LQ== dependencies: aproba "^1.0.3 || ^2.0.0" color-support "^1.1.3" @@ -3455,16 +3416,7 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" -glob@^10.2.2, glob@^10.3.10: - version "10.3.10" - dependencies: - foreground-child "^3.1.0" - jackspeak "^2.3.5" - minimatch "^9.0.1" - minipass "^5.0.0 || ^6.0.2 || ^7.0.0" - path-scurry "^1.10.1" - -glob@^7.1.3, glob@^7.1.4, glob@7.2.3: +glob@7.2.3, glob@^7.1.3, glob@^7.1.4: version "7.2.3" resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -3476,6 +3428,18 @@ glob@^7.1.3, glob@^7.1.4, glob@7.2.3: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^10.2.2, glob@^10.3.10: + version "10.5.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.5.0.tgz#8ec0355919cd3338c28428a23d4f24ecc5fe738c" + integrity sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" + glob@^8.0.1: version "8.1.0" resolved "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz" @@ -3556,10 +3520,10 @@ globby@^14.0.0: slash "^5.1.0" unicorn-magic "^0.1.0" -google-protobuf@^3.21.4: - version "3.21.4" - resolved "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.4.tgz" - integrity sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ== +google-protobuf@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-4.0.1.tgz#9e5935307ead0e32714e33f33a3caac9bd9b49b4" + integrity sha512-0I4mx3UtAwpcc8l9Ds5gHcwV2FyPdwvxxBcor8LusE4b6PjoFxEngZRBepjvuQ4B0eZ2iu/Vezh8oRsnpJ9UGA== gopd@^1.0.1: version "1.0.1" @@ -3568,21 +3532,16 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.9: - version "4.2.11" - resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -graceful-fs@^4.2.11, graceful-fs@^4.2.6: - version "4.2.11" - resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - graceful-fs@4.2.10: version "4.2.10" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + graphemer@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz" @@ -3784,7 +3743,9 @@ ieee754@^1.1.13: integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== ignore-walk@^6.0.0: - version "6.0.4" + version "6.0.5" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-6.0.5.tgz#ef8d61eab7da169078723d1f82833b36e200b0dd" + integrity sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A== dependencies: minimatch "^9.0.0" @@ -3847,23 +3808,25 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3, inherits@2: +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +ini@4.1.1: + version "4.1.1" + resolved "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz" + integrity sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g== + ini@^1.3.4, ini@~1.3.0: version "1.3.8" resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== ini@^4.1.0, ini@^4.1.1: - version "4.1.1" - -ini@4.1.1: - version "4.1.1" - resolved "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz" - integrity sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g== + version "4.1.3" + resolved "https://registry.yarnpkg.com/ini/-/ini-4.1.3.tgz#4c359675a6071a46985eb39b14e4a2c0ec98a795" + integrity sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg== init-package-json@^5.0.0: version "5.0.0" @@ -3916,13 +3879,10 @@ into-stream@^7.0.0: from2 "^2.3.0" p-is-promise "^3.0.0" -ip-address@^9.0.5: - version "9.0.5" - resolved "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz" - integrity sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g== - dependencies: - jsbn "1.1.0" - sprintf-js "^1.1.3" +ip-address@^10.0.1: + version "10.1.0" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-10.1.0.tgz#d8dcffb34d0e02eb241427444a6e23f5b0595aa4" + integrity sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q== ip-regex@^4.1.0: version "4.3.0" @@ -4213,8 +4173,10 @@ istanbul-reports@^3.1.3: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -jackspeak@^2.3.5: - version "2.3.6" +jackspeak@^3.1.2: + version "3.4.3" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" + integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== dependencies: "@isaacs/cliui" "^8.0.2" optionalDependencies: @@ -4625,11 +4587,6 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" -jsbn@1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz" - integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== - jsesc@^2.5.1: version "2.5.2" resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz" @@ -4650,14 +4607,11 @@ json-parse-even-better-errors@^2.3.0: resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== -json-parse-even-better-errors@^3.0.0: +json-parse-even-better-errors@^3.0.0, json-parse-even-better-errors@^3.0.1: version "3.0.2" resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz" integrity sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ== -json-parse-even-better-errors@^3.0.1: - version "3.0.1" - json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" @@ -4704,24 +4658,11 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" -jsonparse@^1.2.0: - version "1.3.1" - resolved "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz" - integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== - -jsonparse@^1.3.1: +jsonparse@^1.2.0, jsonparse@^1.3.1: version "1.3.1" resolved "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz" integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== -JSONStream@^1.3.5: - version "1.3.5" - resolved "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz" - integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== - dependencies: - jsonparse "^1.2.0" - through ">=2.2.7 <3" - just-diff-apply@^5.2.0: version "5.5.0" resolved "https://registry.npmjs.org/just-diff-apply/-/just-diff-apply-5.5.0.tgz" @@ -4989,7 +4930,7 @@ lodash.uniqby@^4.7.0: resolved "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz" integrity sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww== -lodash@^4.17.21, lodash@^4.17.4, lodash@4.17.21: +lodash@4.17.21, lodash@^4.17.21, lodash@^4.17.4: version "4.17.21" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -5012,7 +4953,7 @@ longest@^2.0.1: resolved "https://registry.npmjs.org/longest/-/longest-2.0.1.tgz" integrity sha512-Ajzxb8CM6WAnFjgiloPsI3bF+WCxcvhdIG3KNA2KN962+tdBsHcuQ4k4qX/EcS/2CRkcc0iAkR956Nib6aXU/Q== -lru-cache@^10.0.1, lru-cache@^10.4.3: +lru-cache@^10.0.1, lru-cache@^10.2.0, lru-cache@^10.4.3: version "10.4.3" resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz" integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== @@ -5036,9 +4977,6 @@ lru-cache@^7.4.4, lru-cache@^7.5.1, lru-cache@^7.7.1: resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz" integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== -"lru-cache@^9.1.1 || ^10.0.0": - version "10.2.0" - make-dir@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz" @@ -5046,7 +4984,7 @@ make-dir@^4.0.0: dependencies: semver "^7.5.3" -make-error@^1.1.1, make-error@1.x: +make-error@1.x, make-error@^1.1.1: version "1.3.6" resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== @@ -5155,16 +5093,16 @@ merge-stream@^2.0.0: resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -merge@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/merge/-/merge-2.1.1.tgz" - integrity sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w== - merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== +merge@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/merge/-/merge-2.1.1.tgz" + integrity sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w== + micromatch@^4.0.0, micromatch@^4.0.2, micromatch@^4.0.4: version "4.0.5" resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz" @@ -5205,28 +5143,7 @@ min-indent@^1.0.0: resolved "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== -minimatch@^3.0.4: - version "3.1.2" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^3.0.5: - version "3.1.2" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^3.1.1: - version "3.1.2" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^3.1.2: +minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -5240,8 +5157,10 @@ minimatch@^5.0.1: dependencies: brace-expansion "^2.0.1" -minimatch@^9.0.0, minimatch@^9.0.1, minimatch@^9.0.3: - version "9.0.3" +minimatch@^9.0.0, minimatch@^9.0.3: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== dependencies: brace-expansion "^2.0.1" @@ -5261,16 +5180,16 @@ minimist-options@4.1.0: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: - version "1.2.8" - resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== - minimist@1.2.7: version "1.2.7" resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz" integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== +minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + minipass-collect@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz" @@ -5290,7 +5209,9 @@ minipass-fetch@^2.0.3: encoding "^0.1.13" minipass-fetch@^3.0.0: - version "3.0.4" + version "3.0.5" + resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-3.0.5.tgz#f0f97e40580affc4a35cc4a1349f05ae36cb1e4c" + integrity sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg== dependencies: minipass "^7.0.3" minipass-sized "^1.0.3" @@ -5334,14 +5255,16 @@ minipass@^3.0.0, minipass@^3.1.1, minipass@^3.1.6: dependencies: yallist "^4.0.0" -"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.3, minipass@^7.0.4: - version "7.0.4" - minipass@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz" integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.3, minipass@^7.0.4, minipass@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" + integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== + minizlib@^2.1.1, minizlib@^2.1.2: version "2.1.2" resolved "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz" @@ -5360,31 +5283,26 @@ modify-values@^1.0.1: resolved "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz" integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== -ms@^2.0.0, ms@^2.1.2: - version "2.1.3" - resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -ms@^2.1.1: - version "2.1.3" - resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - ms@2.1.2: version "2.1.2" resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -mute-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz" - integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA== +ms@^2.0.0, ms@^2.1.1, ms@^2.1.2: + version "2.1.3" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== mute-stream@0.0.8: version "0.0.8" resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== +mute-stream@^1.0.0, mute-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz" + integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" @@ -5447,7 +5365,9 @@ nopt@^6.0.0: abbrev "^1.0.0" nopt@^7.0.0, nopt@^7.2.0: - version "7.2.0" + version "7.2.1" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-7.2.1.tgz#1cac0eab9b8e97c9093338446eddd40b2c8ca1e7" + integrity sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w== dependencies: abbrev "^2.0.0" @@ -5894,6 +5814,11 @@ p-try@^2.0.0: resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +package-json-from-dist@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" + integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== + pacote@^15.0.0, pacote@^15.0.8, pacote@^15.2.0: version "15.2.0" resolved "https://registry.npmjs.org/pacote/-/pacote-15.2.0.tgz" @@ -6003,10 +5928,12 @@ path-parse@^1.0.7: resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-scurry@^1.10.1: - version "1.10.1" +path-scurry@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== dependencies: - lru-cache "^9.1.1 || ^10.0.0" + lru-cache "^10.2.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" path-type@^4.0.0: @@ -6060,7 +5987,9 @@ possible-typed-array-names@^1.0.0: integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== postcss-selector-parser@^6.0.10: - version "6.0.15" + version "6.1.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz#27ecb41fb0e3b6ba7a1ec84fff347f734c7929de" + integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg== dependencies: cssesc "^3.0.0" util-deprecate "^1.0.2" @@ -6133,9 +6062,11 @@ prompts@^2.0.1: sisteransi "^1.0.5" promzard@^1.0.0: - version "1.0.0" + version "1.0.2" + resolved "https://registry.yarnpkg.com/promzard/-/promzard-1.0.2.tgz#2226e7c6508b1da3471008ae17066a7c3251e660" + integrity sha512-2FPputGL+mP3jJ3UZg/Dl9YOkovB7DX0oOr+ck5QbZ5MtORtds8k/BZdn+02peDLI8/YWbmzx34k5fA+fHvCVQ== dependencies: - read "^2.0.0" + read "^3.0.1" proto-list@~1.2.1: version "1.2.4" @@ -6273,6 +6204,13 @@ read@^2.0.0, read@^2.1.0: dependencies: mute-stream "~1.0.0" +read@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/read/-/read-3.0.1.tgz#926808f0f7c83fa95f1ef33c0e2c09dbb28fd192" + integrity sha512-SLBrDU/Srs/9EoWhU5GdbAoxG1GzpQHo/6qiGItaoLJ1thmYpcNIM1qISEUvyHBzfGlWIyd6p2DNi1oV1VmAuw== + dependencies: + mute-stream "^1.0.0" + readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@~2.3.6: version "2.3.8" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz" @@ -6286,16 +6224,7 @@ readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@~2.3.6: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.4.0: - version "3.6.2" - resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readable-stream@^3.6.0: +readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.2" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -6458,12 +6387,7 @@ safe-regex-test@^1.0.3: es-errors "^1.3.0" is-regex "^1.1.4" -"safer-buffer@>= 2.1.2 < 3": - version "2.1.2" - resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -"safer-buffer@>= 2.1.2 < 3.0.0": +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -6514,12 +6438,12 @@ semver-regex@^4.0.5: resolved "https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz" integrity sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw== -semver@^6.3.0: - version "6.3.1" - resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== +"semver@2 || 3 || 4 || 5": + version "5.7.2" + resolved "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@^6.3.1: +semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== @@ -6531,11 +6455,6 @@ semver@^7.0.0, semver@^7.1.1, semver@^7.1.2, semver@^7.3.2, semver@^7.3.4, semve dependencies: lru-cache "^6.0.0" -"semver@2 || 3 || 4 || 5": - version "5.7.2" - resolved "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz" - integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== - set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" @@ -6590,12 +6509,7 @@ signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== -signal-exit@^4.0.1: - version "4.1.0" - resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz" - integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== - -signal-exit@^4.1.0: +signal-exit@^4.0.1, signal-exit@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== @@ -6650,9 +6564,11 @@ socks-proxy-agent@^7.0.0: socks "^2.6.2" socks@^2.6.2: - version "2.8.1" + version "2.8.7" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.7.tgz#e2fb1d9a603add75050a2067db8c381a0b5669ea" + integrity sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A== dependencies: - ip-address "^9.0.5" + ip-address "^10.0.1" smart-buffer "^4.2.0" source-map-support@0.5.13: @@ -6704,13 +6620,6 @@ spdx-license-ids@^3.0.0: resolved "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz" integrity sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg== -split@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/split/-/split-1.0.1.tgz" - integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== - dependencies: - through "2" - split2@^4.0.0: version "4.2.0" resolved "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz" @@ -6723,10 +6632,12 @@ split2@~1.0.0: dependencies: through2 "~2.0.0" -sprintf-js@^1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz" - integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== +split@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/split/-/split-1.0.1.tgz" + integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== + dependencies: + through "2" sprintf-js@~1.0.2: version "1.0.3" @@ -6734,7 +6645,9 @@ sprintf-js@~1.0.2: integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== ssri@^10.0.0, ssri@^10.0.1, ssri@^10.0.5: - version "10.0.5" + version "10.0.6" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-10.0.6.tgz#a8aade2de60ba2bce8688e3fa349bad05c7dc1e5" + integrity sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ== dependencies: minipass "^7.0.3" @@ -6760,20 +6673,6 @@ stream-combiner2@~1.1.1: duplexer2 "~0.1.0" readable-stream "^2.0.2" -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - string-length@^4.0.1: version "4.0.2" resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz" @@ -6800,16 +6699,7 @@ string-length@^4.0.1: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^5.0.1: - version "5.1.2" - resolved "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz" - integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== - dependencies: - eastasianwidth "^0.2.0" - emoji-regex "^9.2.2" - strip-ansi "^7.0.1" - -string-width@^5.1.2: +string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz" integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== @@ -6846,6 +6736,20 @@ string.prototype.trimstart@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + "strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" @@ -6867,20 +6771,15 @@ strip-ansi@^7.0.1: dependencies: ansi-regex "^6.0.1" -strip-bom@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz" - integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== - -strip-bom@^4.0.0: +strip-bom@4.0.0, strip-bom@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz" integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== -strip-bom@4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz" - integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== strip-final-newline@^2.0.0: version "2.0.0" @@ -6899,7 +6798,7 @@ strip-indent@^3.0.0: dependencies: min-indent "^1.0.0" -strip-json-comments@^3.1.1, strip-json-comments@3.1.1: +strip-json-comments@3.1.1, strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== @@ -6962,7 +6861,9 @@ tapable@^2.2.0: integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== tar@^6.1.11, tar@^6.1.13, tar@^6.1.2, tar@^6.2.0: - version "6.2.0" + version "6.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" + integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" @@ -7000,21 +6901,11 @@ text-extensions@^2.0.0: resolved "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz" integrity sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g== -text-table@^0.2.0: +text-table@^0.2.0, text-table@~0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== -text-table@~0.2.0: - version "0.2.0" - resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" - integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== - -through@^2.3.6, "through@>=2.2.7 <3", through@2: - version "2.3.8" - resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" - integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== - through2@~2.0.0: version "2.0.5" resolved "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz" @@ -7023,6 +6914,11 @@ through2@~2.0.0: readable-stream "~2.3.6" xtend "~4.0.1" +through@2, "through@>=2.2.7 <3", through@^2.3.6: + version "2.3.8" + resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + tiny-relative-date@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/tiny-relative-date/-/tiny-relative-date-1.3.0.tgz" @@ -7417,14 +7313,7 @@ walker@^1.0.8: dependencies: makeerror "1.0.12" -wcwidth@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz" - integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg== - dependencies: - defaults "^1.0.3" - -wcwidth@^1.0.1: +wcwidth@^1.0.0, wcwidth@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz" integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg== @@ -7460,14 +7349,7 @@ which@^1.2.14: dependencies: isexe "^2.0.0" -which@^2.0.1: - version "2.0.2" - resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -which@^2.0.2: +which@^2.0.1, which@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== From 8a9673d989f5383e07d2d42cc2844cc90384266f Mon Sep 17 00:00:00 2001 From: Niv Date: Tue, 23 Dec 2025 17:58:11 +0200 Subject: [PATCH 02/16] warp spicedb lookupresource --- src/index.ts | 1 + ...titlements.client.lookup-resources.spec.ts | 300 ++++++++++++++++++ src/spicedb/spicedb-entitlements.client.ts | 19 ++ .../spicedb-queries/lookup-request.builder.ts | 28 ++ .../spicedb-queries/lookup-response.mapper.ts | 40 +++ src/types/index.ts | 1 + src/types/lookup.types.ts | 32 ++ 7 files changed, 421 insertions(+) create mode 100644 src/spicedb/spicedb-entitlements.client.lookup-resources.spec.ts create mode 100644 src/spicedb/spicedb-queries/lookup-request.builder.ts create mode 100644 src/spicedb/spicedb-queries/lookup-response.mapper.ts create mode 100644 src/types/lookup.types.ts diff --git a/src/index.ts b/src/index.ts index 5f54614..068f32d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,3 +4,4 @@ export * from './types'; export * from './client-configuration'; export * from './entitlements.client'; export * from './entitlements-client-factory'; +export * from './spicedb/spicedb-entitlements.client'; diff --git a/src/spicedb/spicedb-entitlements.client.lookup-resources.spec.ts b/src/spicedb/spicedb-entitlements.client.lookup-resources.spec.ts new file mode 100644 index 0000000..54fc1e2 --- /dev/null +++ b/src/spicedb/spicedb-entitlements.client.lookup-resources.spec.ts @@ -0,0 +1,300 @@ +import { SpiceDBEntitlementsClient } from './spicedb-entitlements.client'; +import { mock, MockProxy } from 'jest-mock-extended'; +import { LoggingClient } from '../logging'; +import { ClientConfiguration } from '../client-configuration'; +import { v1 } from '@authzed/authzed-node'; +import { LookupResourcesRequest } from '../types'; + +describe('SpiceDBEntitlementsClient.lookupResources', () => { + let mockSpiceClient: MockProxy; + let mockLoggingClient: MockProxy; + let client: SpiceDBEntitlementsClient; + + const mockClientConfig: ClientConfiguration = { + spiceDBEndpoint: 'mock-endpoint', + spiceDBToken: 'mock-token' + }; + + const defaultRequest: LookupResourcesRequest = { + subjectType: 'user', + subjectId: 'user-123', + resourceType: 'document', + permission: 'read' + }; + + beforeEach(() => { + mockSpiceClient = mock(); + mockLoggingClient = mock(); + client = new SpiceDBEntitlementsClient(mockClientConfig, mockLoggingClient, false); + // Replace the internal spiceClient with our mock + (client as any).spiceClient = mockSpiceClient; + }); + + describe('successful lookups', () => { + it('should return resources with correct structure', async () => { + const mockResults: v1.LookupResourcesResponse[] = [ + { + lookedUpAt: undefined, + resourceObjectId: 'resource-1', + permissionship: v1.LookupPermissionship.HAS_PERMISSION, + partialCaveatInfo: undefined, + afterResultCursor: undefined + }, + { + lookedUpAt: undefined, + resourceObjectId: 'resource-2', + permissionship: v1.LookupPermissionship.CONDITIONAL_PERMISSION, + partialCaveatInfo: undefined, + afterResultCursor: undefined + } + ]; + mockSpiceClient.lookupResources.mockResolvedValue(mockResults); + + const result = await client.lookupResources(defaultRequest); + + expect(result.resources).toHaveLength(2); + expect(result.resources[0]).toEqual({ + resourceType: 'document', + resourceId: 'resource-1', + permissionship: 'HAS_PERMISSION' + }); + expect(result.resources[1]).toEqual({ + resourceType: 'document', + resourceId: 'resource-2', + permissionship: 'CONDITIONAL_PERMISSION' + }); + expect(result.totalReturned).toBe(2); + expect(result.cursor).toBeUndefined(); + }); + + it('should return empty resources array when no results', async () => { + mockSpiceClient.lookupResources.mockResolvedValue([]); + + const result = await client.lookupResources(defaultRequest); + + expect(result.resources).toHaveLength(0); + expect(result.totalReturned).toBe(0); + expect(result.cursor).toBeUndefined(); + }); + + it('should use default limit of 50 when not provided', async () => { + mockSpiceClient.lookupResources.mockResolvedValue([]); + + await client.lookupResources(defaultRequest); + + expect(mockSpiceClient.lookupResources).toHaveBeenCalledWith( + expect.objectContaining({ + optionalLimit: 50 + }) + ); + }); + + it('should use provided limit', async () => { + mockSpiceClient.lookupResources.mockResolvedValue([]); + + await client.lookupResources({ ...defaultRequest, options: { limit: 100 } }); + + expect(mockSpiceClient.lookupResources).toHaveBeenCalledWith( + expect.objectContaining({ + optionalLimit: 100 + }) + ); + }); + + it('should build correct SpiceDB request', async () => { + mockSpiceClient.lookupResources.mockResolvedValue([]); + + await client.lookupResources(defaultRequest); + + expect(mockSpiceClient.lookupResources).toHaveBeenCalledWith( + expect.objectContaining({ + resourceObjectType: 'document', + permission: 'read', + subject: expect.objectContaining({ + object: expect.objectContaining({ + objectType: 'user', + objectId: 'user-123' + }), + optionalRelation: '' + }) + }) + ); + }); + }); + + describe('pagination', () => { + it('should return cursor when results equal limit and have afterResultCursor', async () => { + // Create 50 mock results (the default limit) with a cursor + const mockResults: v1.LookupResourcesResponse[] = Array.from({ length: 50 }, (_, i) => ({ + lookedUpAt: undefined, + resourceObjectId: `resource-${i + 1}`, + permissionship: v1.LookupPermissionship.HAS_PERMISSION, + partialCaveatInfo: undefined, + afterResultCursor: i === 49 ? { token: 'next-page-token' } : undefined + })); + mockSpiceClient.lookupResources.mockResolvedValue(mockResults); + + const result = await client.lookupResources(defaultRequest); + + expect(result.cursor).toBe('next-page-token'); // Returns cursor because results.length (50) === limit (50) + expect(result.totalReturned).toBe(50); + }); + + it('should not return cursor when results are less than limit', async () => { + const mockResults: v1.LookupResourcesResponse[] = [ + { + lookedUpAt: undefined, + resourceObjectId: 'resource-1', + permissionship: v1.LookupPermissionship.HAS_PERMISSION, + partialCaveatInfo: undefined, + afterResultCursor: { token: 'next-page-token' } + } + ]; + mockSpiceClient.lookupResources.mockResolvedValue(mockResults); + + const result = await client.lookupResources(defaultRequest); + + expect(result.cursor).toBeUndefined(); // No cursor because results.length (1) < limit (50) + expect(result.totalReturned).toBe(1); + }); + + it('should return no cursor when SpiceDB provides none', async () => { + const mockResults: v1.LookupResourcesResponse[] = [ + { + lookedUpAt: undefined, + resourceObjectId: 'resource-1', + permissionship: v1.LookupPermissionship.HAS_PERMISSION, + partialCaveatInfo: undefined, + afterResultCursor: undefined // No cursor + } + ]; + mockSpiceClient.lookupResources.mockResolvedValue(mockResults); + + const result = await client.lookupResources(defaultRequest); + + expect(result.cursor).toBeUndefined(); + expect(result.totalReturned).toBe(1); + }); + + it('should pass cursor to SpiceDB request when provided', async () => { + mockSpiceClient.lookupResources.mockResolvedValue([]); + + await client.lookupResources({ + ...defaultRequest, + options: { cursor: 'existing-cursor-token' } + }); + + expect(mockSpiceClient.lookupResources).toHaveBeenCalledWith( + expect.objectContaining({ + optionalCursor: expect.objectContaining({ + token: 'existing-cursor-token' + }) + }) + ); + }); + + it('should not pass cursor when not provided', async () => { + mockSpiceClient.lookupResources.mockResolvedValue([]); + + await client.lookupResources(defaultRequest); + + const callArg = mockSpiceClient.lookupResources.mock.calls[0][0]; + expect(callArg.optionalCursor).toBeUndefined(); + }); + }); + + describe('permissionship mapping', () => { + it('should map HAS_PERMISSION correctly', async () => { + const mockResults: v1.LookupResourcesResponse[] = [ + { + lookedUpAt: undefined, + resourceObjectId: 'resource-1', + permissionship: v1.LookupPermissionship.HAS_PERMISSION, + partialCaveatInfo: undefined, + afterResultCursor: undefined + } + ]; + mockSpiceClient.lookupResources.mockResolvedValue(mockResults); + + const result = await client.lookupResources(defaultRequest); + + expect(result.resources[0].permissionship).toBe('HAS_PERMISSION'); + }); + + it('should map CONDITIONAL_PERMISSION correctly', async () => { + const mockResults: v1.LookupResourcesResponse[] = [ + { + lookedUpAt: undefined, + resourceObjectId: 'resource-1', + permissionship: v1.LookupPermissionship.CONDITIONAL_PERMISSION, + partialCaveatInfo: undefined, + afterResultCursor: undefined + } + ]; + mockSpiceClient.lookupResources.mockResolvedValue(mockResults); + + const result = await client.lookupResources(defaultRequest); + + expect(result.resources[0].permissionship).toBe('CONDITIONAL_PERMISSION'); + }); + + it('should map UNSPECIFIED to undefined', async () => { + const mockResults: v1.LookupResourcesResponse[] = [ + { + lookedUpAt: undefined, + resourceObjectId: 'resource-1', + permissionship: v1.LookupPermissionship.UNSPECIFIED, + partialCaveatInfo: undefined, + afterResultCursor: undefined + } + ]; + mockSpiceClient.lookupResources.mockResolvedValue(mockResults); + + const result = await client.lookupResources(defaultRequest); + + expect(result.resources[0].permissionship).toBeUndefined(); + }); + }); + + describe('error handling', () => { + it('should propagate SpiceDB errors', async () => { + const spiceDBError = new Error('SpiceDB error'); + mockSpiceClient.lookupResources.mockRejectedValue(spiceDBError); + + await expect(client.lookupResources(defaultRequest)).rejects.toThrow('SpiceDB error'); + }); + }); + + describe('result ordering', () => { + it('should preserve the order of results from SpiceDB', async () => { + const mockResults: v1.LookupResourcesResponse[] = [ + { + lookedUpAt: undefined, + resourceObjectId: 'first', + permissionship: v1.LookupPermissionship.HAS_PERMISSION, + partialCaveatInfo: undefined, + afterResultCursor: undefined + }, + { + lookedUpAt: undefined, + resourceObjectId: 'second', + permissionship: v1.LookupPermissionship.HAS_PERMISSION, + partialCaveatInfo: undefined, + afterResultCursor: undefined + }, + { + lookedUpAt: undefined, + resourceObjectId: 'third', + permissionship: v1.LookupPermissionship.HAS_PERMISSION, + partialCaveatInfo: undefined, + afterResultCursor: undefined + } + ]; + mockSpiceClient.lookupResources.mockResolvedValue(mockResults); + + const result = await client.lookupResources(defaultRequest); + + expect(result.resources.map((r) => r.resourceId)).toEqual(['first', 'second', 'third']); + }); + }); +}); diff --git a/src/spicedb/spicedb-entitlements.client.ts b/src/spicedb/spicedb-entitlements.client.ts index 5a5acc0..c2a5542 100644 --- a/src/spicedb/spicedb-entitlements.client.ts +++ b/src/spicedb/spicedb-entitlements.client.ts @@ -3,6 +3,8 @@ import { EntitlementsResult, EntityEntitlementsContext, FeatureEntitlementsContext, + LookupResourcesRequest, + LookupResourcesResponse, PermissionsEntitlementsContext, RequestContext, RequestContextType, @@ -12,6 +14,8 @@ import { import { LoggingClient } from '../logging'; import { SpiceDBQueryClient } from './spicedb-queries/spicedb-query.client'; import { v1 } from '@authzed/authzed-node'; +import { buildLookupResourcesRequest } from './spicedb-queries/lookup-request.builder'; +import { mapLookupResourcesResponse } from './spicedb-queries/lookup-response.mapper'; export class SpiceDBEntitlementsClient { private static readonly MONITORING_RESULT: EntitlementsResult = { monitoring: true, result: true }; @@ -53,6 +57,21 @@ export class SpiceDBEntitlementsClient { } } + public async lookupResources(req: LookupResourcesRequest): Promise { + const limit = req.options?.limit ?? 50; + const request = buildLookupResourcesRequest({ + subjectType: req.subjectType, + subjectId: req.subjectId, + resourceType: req.resourceType, + permission: req.permission, + limit, + cursor: req.options?.cursor + }); + + const results = await this.spiceClient.lookupResources(request); + return mapLookupResourcesResponse(results, req.resourceType, limit); + } + private async constructFallbackResult(requestContext: RequestContext): Promise { const fallback = this.fallbackConfiguration instanceof Function diff --git a/src/spicedb/spicedb-queries/lookup-request.builder.ts b/src/spicedb/spicedb-queries/lookup-request.builder.ts new file mode 100644 index 0000000..df8bdfe --- /dev/null +++ b/src/spicedb/spicedb-queries/lookup-request.builder.ts @@ -0,0 +1,28 @@ +import { v1 } from '@authzed/authzed-node'; + +export interface LookupResourcesParams { + subjectType: string; + subjectId: string; + resourceType: string; + permission: string; + limit: number; + cursor?: string; +} + +export function buildLookupResourcesRequest(params: LookupResourcesParams): v1.LookupResourcesRequest { + const { subjectType, subjectId, resourceType, permission, limit, cursor } = params; + + return v1.LookupResourcesRequest.create({ + resourceObjectType: resourceType, + permission: permission, + subject: { + object: { + objectType: subjectType, + objectId: subjectId + }, + optionalRelation: '' + }, + optionalLimit: limit, + optionalCursor: cursor ? v1.Cursor.create({ token: cursor }) : undefined + }); +} diff --git a/src/spicedb/spicedb-queries/lookup-response.mapper.ts b/src/spicedb/spicedb-queries/lookup-response.mapper.ts new file mode 100644 index 0000000..c46818b --- /dev/null +++ b/src/spicedb/spicedb-queries/lookup-response.mapper.ts @@ -0,0 +1,40 @@ +import { v1 } from '@authzed/authzed-node'; +import { LookupResourceItem, LookupResourcesResponse, Permissionship } from '../../types'; + +export function mapPermissionship(permissionship: v1.LookupPermissionship): Permissionship | undefined { + switch (permissionship) { + case v1.LookupPermissionship.HAS_PERMISSION: + return 'HAS_PERMISSION'; + case v1.LookupPermissionship.CONDITIONAL_PERMISSION: + return 'CONDITIONAL_PERMISSION'; + case v1.LookupPermissionship.UNSPECIFIED: + return undefined; + default: + return undefined; + } +} + +export function mapLookupResourcesResponse( + results: v1.LookupResourcesResponse[], + resourceType: string, + limit: number +): LookupResourcesResponse { + const resources: LookupResourceItem[] = results.map((result) => ({ + resourceType, + resourceId: result.resourceObjectId, + permissionship: mapPermissionship(result.permissionship) + })); + + const lastResult = results.length > 0 ? results[results.length - 1] : undefined; + const nextCursor = lastResult?.afterResultCursor?.token; + + // Only return cursor if we hit the limit (meaning there might be more results) + // If we returned fewer than limit, don't provide cursor since there are no more results + const shouldReturnCursor = results.length === limit; + + return { + resources, + cursor: shouldReturnCursor ? nextCursor : undefined, + totalReturned: resources.length + }; +} diff --git a/src/types/index.ts b/src/types/index.ts index 0d9b87b..2b3abe1 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,6 +1,7 @@ export * from './entitlements-justifications.enum'; export * from './entitlements-query'; export * from './entitlements-result'; +export * from './lookup.types'; export * from './opa.dto'; export * from './request-context'; export * from './request-context-type.enum'; diff --git a/src/types/lookup.types.ts b/src/types/lookup.types.ts new file mode 100644 index 0000000..42faa98 --- /dev/null +++ b/src/types/lookup.types.ts @@ -0,0 +1,32 @@ +export type Permissionship = 'HAS_PERMISSION' | 'CONDITIONAL_PERMISSION' | 'NO_PERMISSION'; + +export interface LookupOptions { + limit?: number; + cursor?: string; +} + +export interface LookupRequest { + permission: string; + options?: LookupOptions; +} + +export interface LookupResponse { + cursor?: string; + totalReturned: number; +} + +export interface LookupResourcesRequest extends LookupRequest { + subjectType: string; + subjectId: string; + resourceType: string; +} + +export interface LookupResourceItem { + resourceType: string; + resourceId: string; + permissionship?: Permissionship; +} + +export interface LookupResourcesResponse extends LookupResponse { + resources: LookupResourceItem[]; +} From 82a77c38119cd4b10e4e3203ec6ecfdfe62d028c Mon Sep 17 00:00:00 2001 From: Niv Date: Wed, 24 Dec 2025 11:16:14 +0200 Subject: [PATCH 03/16] added defualt value for limit, added logs --- src/logging/logging-client.ts | 1 + src/logging/simple-logging.client.ts | 4 +++ src/spicedb/spicedb-entitlements.client.ts | 32 ++++++++++++------- .../spicedb-queries/lookup-response.mapper.ts | 4 --- 4 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/logging/logging-client.ts b/src/logging/logging-client.ts index 10bf1c8..04571b0 100644 --- a/src/logging/logging-client.ts +++ b/src/logging/logging-client.ts @@ -7,5 +7,6 @@ export interface LoggingClient { requestContext: RequestContext, queryResult: OpaResponse | SpiceDBResponse ): void | Promise; + logRequest(request: TRequest, response: TResponse): void | Promise; error(error: unknown): void | Promise; } diff --git a/src/logging/simple-logging.client.ts b/src/logging/simple-logging.client.ts index 644a129..d82f6d7 100644 --- a/src/logging/simple-logging.client.ts +++ b/src/logging/simple-logging.client.ts @@ -10,6 +10,10 @@ export class SimpleLoggingClient implements LoggingClient { console.log({ subjectContext, requestContext, queryResult: queryResult.result }); } + public logRequest(request: TRequest, response: TResponse): void { + console.log(JSON.stringify({ request, response }, null, 2)); + } + public error(error: unknown): void { console.error(error); } diff --git a/src/spicedb/spicedb-entitlements.client.ts b/src/spicedb/spicedb-entitlements.client.ts index c2a5542..efbec7d 100644 --- a/src/spicedb/spicedb-entitlements.client.ts +++ b/src/spicedb/spicedb-entitlements.client.ts @@ -58,18 +58,28 @@ export class SpiceDBEntitlementsClient { } public async lookupResources(req: LookupResourcesRequest): Promise { - const limit = req.options?.limit ?? 50; - const request = buildLookupResourcesRequest({ - subjectType: req.subjectType, - subjectId: req.subjectId, - resourceType: req.resourceType, - permission: req.permission, - limit, - cursor: req.options?.cursor - }); + try { + const DEFAULT_LIMIT = 50; + const limit = req.options?.limit || DEFAULT_LIMIT; + const request = buildLookupResourcesRequest({ + subjectType: req.subjectType, + subjectId: req.subjectId, + resourceType: req.resourceType, + permission: req.permission, + limit, + cursor: req.options?.cursor + }); + + const results = await this.spiceClient.lookupResources(request); - const results = await this.spiceClient.lookupResources(request); - return mapLookupResourcesResponse(results, req.resourceType, limit); + if (this.logResults) { + await this.loggingClient.logRequest(request, results); + } + return mapLookupResourcesResponse(results, req.resourceType, limit); + } catch (err) { + await this.loggingClient.error(err); + throw err; + } } private async constructFallbackResult(requestContext: RequestContext): Promise { diff --git a/src/spicedb/spicedb-queries/lookup-response.mapper.ts b/src/spicedb/spicedb-queries/lookup-response.mapper.ts index c46818b..44ca83c 100644 --- a/src/spicedb/spicedb-queries/lookup-response.mapper.ts +++ b/src/spicedb/spicedb-queries/lookup-response.mapper.ts @@ -27,11 +27,7 @@ export function mapLookupResourcesResponse( const lastResult = results.length > 0 ? results[results.length - 1] : undefined; const nextCursor = lastResult?.afterResultCursor?.token; - - // Only return cursor if we hit the limit (meaning there might be more results) - // If we returned fewer than limit, don't provide cursor since there are no more results const shouldReturnCursor = results.length === limit; - return { resources, cursor: shouldReturnCursor ? nextCursor : undefined, From 06d5f2aae29f661ac6eb4d3aa6ae4d1c516629fe Mon Sep 17 00:00:00 2001 From: Niv Date: Wed, 24 Dec 2025 11:42:08 +0200 Subject: [PATCH 04/16] added lookupsubject --- src/spicedb/spicedb-entitlements.client.ts | 27 +++++++++++++++++-- .../spicedb-queries/lookup-request.builder.ts | 20 ++++++++++++++ .../spicedb-queries/lookup-response.mapper.ts | 24 ++++++++++++++++- src/types/lookup.types.ts | 16 +++++++++++ 4 files changed, 84 insertions(+), 3 deletions(-) diff --git a/src/spicedb/spicedb-entitlements.client.ts b/src/spicedb/spicedb-entitlements.client.ts index efbec7d..63906a6 100644 --- a/src/spicedb/spicedb-entitlements.client.ts +++ b/src/spicedb/spicedb-entitlements.client.ts @@ -5,6 +5,8 @@ import { FeatureEntitlementsContext, LookupResourcesRequest, LookupResourcesResponse, + LookupSubjectsRequest, + LookupSubjectsResponse, PermissionsEntitlementsContext, RequestContext, RequestContextType, @@ -14,8 +16,8 @@ import { import { LoggingClient } from '../logging'; import { SpiceDBQueryClient } from './spicedb-queries/spicedb-query.client'; import { v1 } from '@authzed/authzed-node'; -import { buildLookupResourcesRequest } from './spicedb-queries/lookup-request.builder'; -import { mapLookupResourcesResponse } from './spicedb-queries/lookup-response.mapper'; +import { buildLookupResourcesRequest, buildLookupSubjectsRequest } from './spicedb-queries/lookup-request.builder'; +import { mapLookupResourcesResponse, mapLookupSubjectsResponse } from './spicedb-queries/lookup-response.mapper'; export class SpiceDBEntitlementsClient { private static readonly MONITORING_RESULT: EntitlementsResult = { monitoring: true, result: true }; @@ -82,6 +84,27 @@ export class SpiceDBEntitlementsClient { } } + public async lookupSubjects(req: LookupSubjectsRequest): Promise { + try { + const request = buildLookupSubjectsRequest({ + resourceType: req.resourceType, + resourceId: req.resourceId, + subjectType: req.subjectType, + permission: req.permission + }); + + const results = await this.spiceClient.lookupSubjects(request); + + if (this.logResults) { + await this.loggingClient.logRequest(request, results); + } + return mapLookupSubjectsResponse(results, req.subjectType); + } catch (err) { + await this.loggingClient.error(err); + throw err; + } + } + private async constructFallbackResult(requestContext: RequestContext): Promise { const fallback = this.fallbackConfiguration instanceof Function diff --git a/src/spicedb/spicedb-queries/lookup-request.builder.ts b/src/spicedb/spicedb-queries/lookup-request.builder.ts index df8bdfe..c205bbe 100644 --- a/src/spicedb/spicedb-queries/lookup-request.builder.ts +++ b/src/spicedb/spicedb-queries/lookup-request.builder.ts @@ -26,3 +26,23 @@ export function buildLookupResourcesRequest(params: LookupResourcesParams): v1.L optionalCursor: cursor ? v1.Cursor.create({ token: cursor }) : undefined }); } + +export interface LookupSubjectsParams { + resourceType: string; + resourceId: string; + subjectType: string; + permission: string; +} + +export function buildLookupSubjectsRequest(params: LookupSubjectsParams): v1.LookupSubjectsRequest { + const { resourceType, resourceId, subjectType, permission } = params; + + return v1.LookupSubjectsRequest.create({ + resource: { + objectType: resourceType, + objectId: resourceId + }, + permission: permission, + subjectObjectType: subjectType + }); +} diff --git a/src/spicedb/spicedb-queries/lookup-response.mapper.ts b/src/spicedb/spicedb-queries/lookup-response.mapper.ts index 44ca83c..50cb4bb 100644 --- a/src/spicedb/spicedb-queries/lookup-response.mapper.ts +++ b/src/spicedb/spicedb-queries/lookup-response.mapper.ts @@ -1,5 +1,11 @@ import { v1 } from '@authzed/authzed-node'; -import { LookupResourceItem, LookupResourcesResponse, Permissionship } from '../../types'; +import { + LookupResourceItem, + LookupResourcesResponse, + LookupSubjectItem, + LookupSubjectsResponse, + Permissionship +} from '../../types'; export function mapPermissionship(permissionship: v1.LookupPermissionship): Permissionship | undefined { switch (permissionship) { @@ -34,3 +40,19 @@ export function mapLookupResourcesResponse( totalReturned: resources.length }; } + +export function mapLookupSubjectsResponse( + results: v1.LookupSubjectsResponse[], + subjectType: string +): LookupSubjectsResponse { + const subjects: LookupSubjectItem[] = results.map((result) => ({ + subjectType, + subjectId: result.subject?.subjectObjectId ?? '', + permissionship: mapPermissionship(result.permissionship) + })); + + return { + subjects, + totalReturned: subjects.length + }; +} diff --git a/src/types/lookup.types.ts b/src/types/lookup.types.ts index 42faa98..50bbc2c 100644 --- a/src/types/lookup.types.ts +++ b/src/types/lookup.types.ts @@ -30,3 +30,19 @@ export interface LookupResourceItem { export interface LookupResourcesResponse extends LookupResponse { resources: LookupResourceItem[]; } + +export interface LookupSubjectsRequest extends LookupRequest { + resourceType: string; + resourceId: string; + subjectType: string; +} + +export interface LookupSubjectItem { + subjectType: string; + subjectId: string; + permissionship?: Permissionship; +} + +export interface LookupSubjectsResponse extends LookupResponse { + subjects: LookupSubjectItem[]; +} From 016b9119c5d45ebd7a8e0b183a8fafa51b8bf74d Mon Sep 17 00:00:00 2001 From: Niv Date: Wed, 24 Dec 2025 11:45:00 +0200 Subject: [PATCH 05/16] cover lookupsujbect with tests --- ...ntitlements.client.lookup-subjects.spec.ts | 300 ++++++++++++++++++ .../lookup-request.builder.spec.ts | 97 ++++++ .../lookup-response.mapper.spec.ts | 199 ++++++++++++ 3 files changed, 596 insertions(+) create mode 100644 src/spicedb/spicedb-entitlements.client.lookup-subjects.spec.ts create mode 100644 src/spicedb/spicedb-queries/lookup-request.builder.spec.ts create mode 100644 src/spicedb/spicedb-queries/lookup-response.mapper.spec.ts diff --git a/src/spicedb/spicedb-entitlements.client.lookup-subjects.spec.ts b/src/spicedb/spicedb-entitlements.client.lookup-subjects.spec.ts new file mode 100644 index 0000000..5921784 --- /dev/null +++ b/src/spicedb/spicedb-entitlements.client.lookup-subjects.spec.ts @@ -0,0 +1,300 @@ +import { SpiceDBEntitlementsClient } from './spicedb-entitlements.client'; +import { mock, MockProxy } from 'jest-mock-extended'; +import { LoggingClient } from '../logging'; +import { ClientConfiguration } from '../client-configuration'; +import { v1 } from '@authzed/authzed-node'; +import { LookupSubjectsRequest } from '../types'; + +describe('SpiceDBEntitlementsClient.lookupSubjects', () => { + let mockSpiceClient: MockProxy; + let mockLoggingClient: MockProxy; + let client: SpiceDBEntitlementsClient; + + const mockClientConfig: ClientConfiguration = { + spiceDBEndpoint: 'mock-endpoint', + spiceDBToken: 'mock-token' + }; + + const defaultRequest: LookupSubjectsRequest = { + resourceType: 'document', + resourceId: 'doc-123', + subjectType: 'user', + permission: 'view' + }; + + beforeEach(() => { + mockSpiceClient = mock(); + mockLoggingClient = mock(); + client = new SpiceDBEntitlementsClient(mockClientConfig, mockLoggingClient, false); + // Replace the internal spiceClient with our mock + (client as any).spiceClient = mockSpiceClient; + }); + + describe('successful lookups', () => { + it('should return subjects with correct structure', async () => { + const mockResults: v1.LookupSubjectsResponse[] = [ + { + lookedUpAt: undefined, + subjectObjectId: 'user-1', + excludedSubjectIds: [], + permissionship: v1.LookupPermissionship.HAS_PERMISSION, + partialCaveatInfo: undefined, + subject: { + subjectObjectId: 'user-1', + permissionship: v1.LookupPermissionship.HAS_PERMISSION, + partialCaveatInfo: undefined + }, + excludedSubjects: [], + afterResultCursor: undefined + }, + { + lookedUpAt: undefined, + subjectObjectId: 'user-2', + excludedSubjectIds: [], + permissionship: v1.LookupPermissionship.CONDITIONAL_PERMISSION, + partialCaveatInfo: undefined, + subject: { + subjectObjectId: 'user-2', + permissionship: v1.LookupPermissionship.CONDITIONAL_PERMISSION, + partialCaveatInfo: undefined + }, + excludedSubjects: [], + afterResultCursor: undefined + } + ]; + mockSpiceClient.lookupSubjects.mockResolvedValue(mockResults); + + const result = await client.lookupSubjects(defaultRequest); + + expect(result.subjects).toHaveLength(2); + expect(result.subjects[0]).toEqual({ + subjectType: 'user', + subjectId: 'user-1', + permissionship: 'HAS_PERMISSION' + }); + expect(result.subjects[1]).toEqual({ + subjectType: 'user', + subjectId: 'user-2', + permissionship: 'CONDITIONAL_PERMISSION' + }); + expect(result.totalReturned).toBe(2); + }); + + it('should return empty subjects array when no results', async () => { + mockSpiceClient.lookupSubjects.mockResolvedValue([]); + + const result = await client.lookupSubjects(defaultRequest); + + expect(result.subjects).toHaveLength(0); + expect(result.totalReturned).toBe(0); + }); + + it('should build correct SpiceDB request', async () => { + mockSpiceClient.lookupSubjects.mockResolvedValue([]); + + await client.lookupSubjects(defaultRequest); + + expect(mockSpiceClient.lookupSubjects).toHaveBeenCalledWith( + expect.objectContaining({ + resource: expect.objectContaining({ + objectType: 'document', + objectId: 'doc-123' + }), + permission: 'view', + subjectObjectType: 'user' + }) + ); + }); + }); + + describe('permissionship mapping', () => { + it('should map HAS_PERMISSION correctly', async () => { + const mockResults: v1.LookupSubjectsResponse[] = [ + { + lookedUpAt: undefined, + subjectObjectId: 'user-1', + excludedSubjectIds: [], + permissionship: v1.LookupPermissionship.HAS_PERMISSION, + partialCaveatInfo: undefined, + subject: { + subjectObjectId: 'user-1', + permissionship: v1.LookupPermissionship.HAS_PERMISSION, + partialCaveatInfo: undefined + }, + excludedSubjects: [], + afterResultCursor: undefined + } + ]; + mockSpiceClient.lookupSubjects.mockResolvedValue(mockResults); + + const result = await client.lookupSubjects(defaultRequest); + + expect(result.subjects[0].permissionship).toBe('HAS_PERMISSION'); + }); + + it('should map CONDITIONAL_PERMISSION correctly', async () => { + const mockResults: v1.LookupSubjectsResponse[] = [ + { + lookedUpAt: undefined, + subjectObjectId: 'user-1', + excludedSubjectIds: [], + permissionship: v1.LookupPermissionship.CONDITIONAL_PERMISSION, + partialCaveatInfo: undefined, + subject: { + subjectObjectId: 'user-1', + permissionship: v1.LookupPermissionship.CONDITIONAL_PERMISSION, + partialCaveatInfo: undefined + }, + excludedSubjects: [], + afterResultCursor: undefined + } + ]; + mockSpiceClient.lookupSubjects.mockResolvedValue(mockResults); + + const result = await client.lookupSubjects(defaultRequest); + + expect(result.subjects[0].permissionship).toBe('CONDITIONAL_PERMISSION'); + }); + + it('should map UNSPECIFIED to undefined', async () => { + const mockResults: v1.LookupSubjectsResponse[] = [ + { + lookedUpAt: undefined, + subjectObjectId: 'user-1', + excludedSubjectIds: [], + permissionship: v1.LookupPermissionship.UNSPECIFIED, + partialCaveatInfo: undefined, + subject: { + subjectObjectId: 'user-1', + permissionship: v1.LookupPermissionship.UNSPECIFIED, + partialCaveatInfo: undefined + }, + excludedSubjects: [], + afterResultCursor: undefined + } + ]; + mockSpiceClient.lookupSubjects.mockResolvedValue(mockResults); + + const result = await client.lookupSubjects(defaultRequest); + + expect(result.subjects[0].permissionship).toBeUndefined(); + }); + + it('should handle missing subject gracefully', async () => { + const mockResults: v1.LookupSubjectsResponse[] = [ + { + lookedUpAt: undefined, + subjectObjectId: 'user-1', + excludedSubjectIds: [], + permissionship: v1.LookupPermissionship.HAS_PERMISSION, + partialCaveatInfo: undefined, + subject: undefined, + excludedSubjects: [], + afterResultCursor: undefined + } + ]; + mockSpiceClient.lookupSubjects.mockResolvedValue(mockResults); + + const result = await client.lookupSubjects(defaultRequest); + + expect(result.subjects[0].subjectId).toBe(''); + expect(result.subjects[0].permissionship).toBeUndefined(); + }); + }); + + describe('error handling', () => { + it('should propagate SpiceDB errors', async () => { + const spiceDBError = new Error('SpiceDB error'); + mockSpiceClient.lookupSubjects.mockRejectedValue(spiceDBError); + + await expect(client.lookupSubjects(defaultRequest)).rejects.toThrow('SpiceDB error'); + }); + + it('should log error before throwing', async () => { + const spiceDBError = new Error('SpiceDB error'); + mockSpiceClient.lookupSubjects.mockRejectedValue(spiceDBError); + + await expect(client.lookupSubjects(defaultRequest)).rejects.toThrow(); + + expect(mockLoggingClient.error).toHaveBeenCalledWith(spiceDBError); + }); + }); + + describe('result ordering', () => { + it('should preserve the order of results from SpiceDB', async () => { + const mockResults: v1.LookupSubjectsResponse[] = [ + { + lookedUpAt: undefined, + subjectObjectId: 'first', + excludedSubjectIds: [], + permissionship: v1.LookupPermissionship.HAS_PERMISSION, + partialCaveatInfo: undefined, + subject: { + subjectObjectId: 'first', + permissionship: v1.LookupPermissionship.HAS_PERMISSION, + partialCaveatInfo: undefined + }, + excludedSubjects: [], + afterResultCursor: undefined + }, + { + lookedUpAt: undefined, + subjectObjectId: 'second', + excludedSubjectIds: [], + permissionship: v1.LookupPermissionship.HAS_PERMISSION, + partialCaveatInfo: undefined, + subject: { + subjectObjectId: 'second', + permissionship: v1.LookupPermissionship.HAS_PERMISSION, + partialCaveatInfo: undefined + }, + excludedSubjects: [], + afterResultCursor: undefined + }, + { + lookedUpAt: undefined, + subjectObjectId: 'third', + excludedSubjectIds: [], + permissionship: v1.LookupPermissionship.HAS_PERMISSION, + partialCaveatInfo: undefined, + subject: { + subjectObjectId: 'third', + permissionship: v1.LookupPermissionship.HAS_PERMISSION, + partialCaveatInfo: undefined + }, + excludedSubjects: [], + afterResultCursor: undefined + } + ]; + mockSpiceClient.lookupSubjects.mockResolvedValue(mockResults); + + const result = await client.lookupSubjects(defaultRequest); + + expect(result.subjects.map((s) => s.subjectId)).toEqual(['first', 'second', 'third']); + }); + }); + + describe('logging', () => { + it('should log request and results when logResults is enabled', async () => { + const clientWithLogging = new SpiceDBEntitlementsClient(mockClientConfig, mockLoggingClient, true); + (clientWithLogging as any).spiceClient = mockSpiceClient; + + const mockResults: v1.LookupSubjectsResponse[] = []; + mockSpiceClient.lookupSubjects.mockResolvedValue(mockResults); + + await clientWithLogging.lookupSubjects(defaultRequest); + + expect(mockLoggingClient.logRequest).toHaveBeenCalledWith(expect.anything(), mockResults); + }); + + it('should not log when logResults is disabled', async () => { + const mockResults: v1.LookupSubjectsResponse[] = []; + mockSpiceClient.lookupSubjects.mockResolvedValue(mockResults); + + await client.lookupSubjects(defaultRequest); + + expect(mockLoggingClient.logRequest).not.toHaveBeenCalled(); + }); + }); +}); + diff --git a/src/spicedb/spicedb-queries/lookup-request.builder.spec.ts b/src/spicedb/spicedb-queries/lookup-request.builder.spec.ts new file mode 100644 index 0000000..b7db5eb --- /dev/null +++ b/src/spicedb/spicedb-queries/lookup-request.builder.spec.ts @@ -0,0 +1,97 @@ +import { v1 } from '@authzed/authzed-node'; +import { buildLookupResourcesRequest, buildLookupSubjectsRequest } from './lookup-request.builder'; + +describe('lookup-request.builder', () => { + describe('buildLookupResourcesRequest', () => { + it('should build request with all parameters', () => { + const result = buildLookupResourcesRequest({ + subjectType: 'user', + subjectId: 'user-123', + resourceType: 'document', + permission: 'read', + limit: 100, + cursor: 'cursor-token' + }); + + expect(result.resourceObjectType).toBe('document'); + expect(result.permission).toBe('read'); + expect(result.subject?.object?.objectType).toBe('user'); + expect(result.subject?.object?.objectId).toBe('user-123'); + expect(result.subject?.optionalRelation).toBe(''); + expect(result.optionalLimit).toBe(100); + expect(result.optionalCursor?.token).toBe('cursor-token'); + }); + + it('should build request without cursor when not provided', () => { + const result = buildLookupResourcesRequest({ + subjectType: 'user', + subjectId: 'user-123', + resourceType: 'document', + permission: 'read', + limit: 50 + }); + + expect(result.optionalCursor).toBeUndefined(); + }); + + it('should create valid v1.LookupResourcesRequest', () => { + const result = buildLookupResourcesRequest({ + subjectType: 'user', + subjectId: 'user-123', + resourceType: 'document', + permission: 'read', + limit: 50 + }); + + // Verify it's a valid LookupResourcesRequest structure + expect(result).toHaveProperty('resourceObjectType'); + expect(result).toHaveProperty('permission'); + expect(result).toHaveProperty('subject'); + expect(result).toHaveProperty('optionalLimit'); + }); + }); + + describe('buildLookupSubjectsRequest', () => { + it('should build request with all parameters', () => { + const result = buildLookupSubjectsRequest({ + resourceType: 'document', + resourceId: 'doc-123', + subjectType: 'user', + permission: 'view' + }); + + expect(result.resource?.objectType).toBe('document'); + expect(result.resource?.objectId).toBe('doc-123'); + expect(result.permission).toBe('view'); + expect(result.subjectObjectType).toBe('user'); + }); + + it('should create valid v1.LookupSubjectsRequest', () => { + const result = buildLookupSubjectsRequest({ + resourceType: 'document', + resourceId: 'doc-123', + subjectType: 'user', + permission: 'view' + }); + + // Verify it's a valid LookupSubjectsRequest structure + expect(result).toHaveProperty('resource'); + expect(result).toHaveProperty('permission'); + expect(result).toHaveProperty('subjectObjectType'); + }); + + it('should handle different resource types', () => { + const result = buildLookupSubjectsRequest({ + resourceType: 'folder', + resourceId: 'folder-456', + subjectType: 'group', + permission: 'admin' + }); + + expect(result.resource?.objectType).toBe('folder'); + expect(result.resource?.objectId).toBe('folder-456'); + expect(result.subjectObjectType).toBe('group'); + expect(result.permission).toBe('admin'); + }); + }); +}); diff --git a/src/spicedb/spicedb-queries/lookup-response.mapper.spec.ts b/src/spicedb/spicedb-queries/lookup-response.mapper.spec.ts new file mode 100644 index 0000000..91ff321 --- /dev/null +++ b/src/spicedb/spicedb-queries/lookup-response.mapper.spec.ts @@ -0,0 +1,199 @@ +import { v1 } from '@authzed/authzed-node'; +import { mapLookupResourcesResponse, mapLookupSubjectsResponse, mapPermissionship } from './lookup-response.mapper'; + +describe('lookup-response.mapper', () => { + describe('mapPermissionship', () => { + it('should map HAS_PERMISSION', () => { + expect(mapPermissionship(v1.LookupPermissionship.HAS_PERMISSION)).toBe('HAS_PERMISSION'); + }); + + it('should map CONDITIONAL_PERMISSION', () => { + expect(mapPermissionship(v1.LookupPermissionship.CONDITIONAL_PERMISSION)).toBe('CONDITIONAL_PERMISSION'); + }); + + it('should map UNSPECIFIED to undefined', () => { + expect(mapPermissionship(v1.LookupPermissionship.UNSPECIFIED)).toBeUndefined(); + }); + + it('should map unknown values to undefined', () => { + expect(mapPermissionship(999 as v1.LookupPermissionship)).toBeUndefined(); + }); + }); + + describe('mapLookupResourcesResponse', () => { + it('should map resources correctly', () => { + const results: v1.LookupResourcesResponse[] = [ + { + lookedUpAt: undefined, + resourceObjectId: 'resource-1', + permissionship: v1.LookupPermissionship.HAS_PERMISSION, + partialCaveatInfo: undefined, + afterResultCursor: undefined + }, + { + lookedUpAt: undefined, + resourceObjectId: 'resource-2', + permissionship: v1.LookupPermissionship.CONDITIONAL_PERMISSION, + partialCaveatInfo: undefined, + afterResultCursor: undefined + } + ]; + + const response = mapLookupResourcesResponse(results, 'document', 50); + + expect(response.resources).toHaveLength(2); + expect(response.resources[0]).toEqual({ + resourceType: 'document', + resourceId: 'resource-1', + permissionship: 'HAS_PERMISSION' + }); + expect(response.resources[1]).toEqual({ + resourceType: 'document', + resourceId: 'resource-2', + permissionship: 'CONDITIONAL_PERMISSION' + }); + expect(response.totalReturned).toBe(2); + }); + + it('should return cursor when results equal limit', () => { + const results: v1.LookupResourcesResponse[] = Array.from({ length: 10 }, (_, i) => ({ + lookedUpAt: undefined, + resourceObjectId: `resource-${i}`, + permissionship: v1.LookupPermissionship.HAS_PERMISSION, + partialCaveatInfo: undefined, + afterResultCursor: i === 9 ? { token: 'next-cursor' } : undefined + })); + + const response = mapLookupResourcesResponse(results, 'document', 10); + + expect(response.cursor).toBe('next-cursor'); + }); + + it('should not return cursor when results less than limit', () => { + const results: v1.LookupResourcesResponse[] = [ + { + lookedUpAt: undefined, + resourceObjectId: 'resource-1', + permissionship: v1.LookupPermissionship.HAS_PERMISSION, + partialCaveatInfo: undefined, + afterResultCursor: { token: 'cursor-token' } + } + ]; + + const response = mapLookupResourcesResponse(results, 'document', 50); + + expect(response.cursor).toBeUndefined(); + }); + + it('should handle empty results', () => { + const response = mapLookupResourcesResponse([], 'document', 50); + + expect(response.resources).toHaveLength(0); + expect(response.totalReturned).toBe(0); + expect(response.cursor).toBeUndefined(); + }); + }); + + describe('mapLookupSubjectsResponse', () => { + it('should map subjects correctly', () => { + const results: v1.LookupSubjectsResponse[] = [ + { + lookedUpAt: undefined, + subjectObjectId: 'user-1', + excludedSubjectIds: [], + permissionship: v1.LookupPermissionship.HAS_PERMISSION, + partialCaveatInfo: undefined, + subject: { + subjectObjectId: 'user-1', + permissionship: v1.LookupPermissionship.HAS_PERMISSION, + partialCaveatInfo: undefined + }, + excludedSubjects: [], + afterResultCursor: undefined + }, + { + lookedUpAt: undefined, + subjectObjectId: 'user-2', + excludedSubjectIds: [], + permissionship: v1.LookupPermissionship.CONDITIONAL_PERMISSION, + partialCaveatInfo: undefined, + subject: { + subjectObjectId: 'user-2', + permissionship: v1.LookupPermissionship.CONDITIONAL_PERMISSION, + partialCaveatInfo: undefined + }, + excludedSubjects: [], + afterResultCursor: undefined + } + ]; + + const response = mapLookupSubjectsResponse(results, 'user'); + + expect(response.subjects).toHaveLength(2); + expect(response.subjects[0]).toEqual({ + subjectType: 'user', + subjectId: 'user-1', + permissionship: 'HAS_PERMISSION' + }); + expect(response.subjects[1]).toEqual({ + subjectType: 'user', + subjectId: 'user-2', + permissionship: 'CONDITIONAL_PERMISSION' + }); + expect(response.totalReturned).toBe(2); + }); + + it('should handle empty results', () => { + const response = mapLookupSubjectsResponse([], 'user'); + + expect(response.subjects).toHaveLength(0); + expect(response.totalReturned).toBe(0); + }); + + it('should handle missing subject field gracefully', () => { + const results: v1.LookupSubjectsResponse[] = [ + { + lookedUpAt: undefined, + subjectObjectId: 'user-1', + excludedSubjectIds: [], + permissionship: v1.LookupPermissionship.HAS_PERMISSION, + partialCaveatInfo: undefined, + subject: undefined, + excludedSubjects: [], + afterResultCursor: undefined + } + ]; + + const response = mapLookupSubjectsResponse(results, 'user'); + + expect(response.subjects[0].subjectId).toBe(''); + expect(response.subjects[0].permissionship).toBeUndefined(); + }); + + it('should use subject.permissionship instead of deprecated top-level field', () => { + const results: v1.LookupSubjectsResponse[] = [ + { + lookedUpAt: undefined, + subjectObjectId: 'user-1', + excludedSubjectIds: [], + // Deprecated top-level field says HAS_PERMISSION + permissionship: v1.LookupPermissionship.HAS_PERMISSION, + partialCaveatInfo: undefined, + // But subject field says CONDITIONAL_PERMISSION + subject: { + subjectObjectId: 'user-1', + permissionship: v1.LookupPermissionship.CONDITIONAL_PERMISSION, + partialCaveatInfo: undefined + }, + excludedSubjects: [], + afterResultCursor: undefined + } + ]; + + const response = mapLookupSubjectsResponse(results, 'user'); + + // Should use the subject field value, not the deprecated top-level one + expect(response.subjects[0].permissionship).toBe('CONDITIONAL_PERMISSION'); + }); + }); +}); From d40808d9b439feafb7ffe719513f75133fd9808e Mon Sep 17 00:00:00 2001 From: Niv Date: Wed, 24 Dec 2025 11:55:10 +0200 Subject: [PATCH 06/16] result.permmision is deprected --- src/spicedb/spicedb-queries/lookup-response.mapper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spicedb/spicedb-queries/lookup-response.mapper.ts b/src/spicedb/spicedb-queries/lookup-response.mapper.ts index 50cb4bb..ddfd8dc 100644 --- a/src/spicedb/spicedb-queries/lookup-response.mapper.ts +++ b/src/spicedb/spicedb-queries/lookup-response.mapper.ts @@ -48,7 +48,7 @@ export function mapLookupSubjectsResponse( const subjects: LookupSubjectItem[] = results.map((result) => ({ subjectType, subjectId: result.subject?.subjectObjectId ?? '', - permissionship: mapPermissionship(result.permissionship) + permissionship: result.subject ? mapPermissionship(result.subject.permissionship) : undefined })); return { From 557629d9066385a14fc5eb0463c5cb408b680c07 Mon Sep 17 00:00:00 2001 From: Niv Date: Wed, 24 Dec 2025 14:51:35 +0200 Subject: [PATCH 07/16] Moved lookups params to types file --- .../spicedb-queries/lookup-request.builder.ts | 17 +---------------- src/types/lookup.types.ts | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/spicedb/spicedb-queries/lookup-request.builder.ts b/src/spicedb/spicedb-queries/lookup-request.builder.ts index c205bbe..8cce2cc 100644 --- a/src/spicedb/spicedb-queries/lookup-request.builder.ts +++ b/src/spicedb/spicedb-queries/lookup-request.builder.ts @@ -1,13 +1,5 @@ import { v1 } from '@authzed/authzed-node'; - -export interface LookupResourcesParams { - subjectType: string; - subjectId: string; - resourceType: string; - permission: string; - limit: number; - cursor?: string; -} +import { LookupResourcesParams, LookupSubjectsParams } from '../../types/lookup.types'; export function buildLookupResourcesRequest(params: LookupResourcesParams): v1.LookupResourcesRequest { const { subjectType, subjectId, resourceType, permission, limit, cursor } = params; @@ -27,13 +19,6 @@ export function buildLookupResourcesRequest(params: LookupResourcesParams): v1.L }); } -export interface LookupSubjectsParams { - resourceType: string; - resourceId: string; - subjectType: string; - permission: string; -} - export function buildLookupSubjectsRequest(params: LookupSubjectsParams): v1.LookupSubjectsRequest { const { resourceType, resourceId, subjectType, permission } = params; diff --git a/src/types/lookup.types.ts b/src/types/lookup.types.ts index 50bbc2c..3e4be23 100644 --- a/src/types/lookup.types.ts +++ b/src/types/lookup.types.ts @@ -46,3 +46,19 @@ export interface LookupSubjectItem { export interface LookupSubjectsResponse extends LookupResponse { subjects: LookupSubjectItem[]; } + +export interface LookupResourcesParams { + subjectType: string; + subjectId: string; + resourceType: string; + permission: string; + limit: number; + cursor?: string; +} + +export interface LookupSubjectsParams { + resourceType: string; + resourceId: string; + subjectType: string; + permission: string; +} From a7ee8f85cd631b6458cf43b9f2ac7d8fd0dd1faa Mon Sep 17 00:00:00 2001 From: Niv Date: Wed, 24 Dec 2025 15:16:09 +0200 Subject: [PATCH 08/16] replaced switch-case with map, moved consts to a consts file, fixed lookup types --- src/spicedb/lookup.constants.ts | 9 +++++++++ ...cedb-entitlements.client.lookup-resources.spec.ts | 4 ++-- src/spicedb/spicedb-entitlements.client.ts | 6 +++--- .../spicedb-queries/lookup-response.mapper.ts | 12 ++---------- src/types/lookup.types.ts | 8 ++------ 5 files changed, 18 insertions(+), 21 deletions(-) create mode 100644 src/spicedb/lookup.constants.ts diff --git a/src/spicedb/lookup.constants.ts b/src/spicedb/lookup.constants.ts new file mode 100644 index 0000000..8c26d24 --- /dev/null +++ b/src/spicedb/lookup.constants.ts @@ -0,0 +1,9 @@ +import { v1 } from '@authzed/authzed-node'; +import { Permissionship } from '../types'; + +export const DEFAULT_LOOKUP_LIMIT = 50; + +export const permissionshipMap = new Map([ + [v1.LookupPermissionship.HAS_PERMISSION, 'HAS_PERMISSION'], + [v1.LookupPermissionship.CONDITIONAL_PERMISSION, 'CONDITIONAL_PERMISSION'] +]); diff --git a/src/spicedb/spicedb-entitlements.client.lookup-resources.spec.ts b/src/spicedb/spicedb-entitlements.client.lookup-resources.spec.ts index 54fc1e2..8ff0f32 100644 --- a/src/spicedb/spicedb-entitlements.client.lookup-resources.spec.ts +++ b/src/spicedb/spicedb-entitlements.client.lookup-resources.spec.ts @@ -92,7 +92,7 @@ describe('SpiceDBEntitlementsClient.lookupResources', () => { it('should use provided limit', async () => { mockSpiceClient.lookupResources.mockResolvedValue([]); - await client.lookupResources({ ...defaultRequest, options: { limit: 100 } }); + await client.lookupResources({ ...defaultRequest, limit: 100 }); expect(mockSpiceClient.lookupResources).toHaveBeenCalledWith( expect.objectContaining({ @@ -181,7 +181,7 @@ describe('SpiceDBEntitlementsClient.lookupResources', () => { await client.lookupResources({ ...defaultRequest, - options: { cursor: 'existing-cursor-token' } + cursor: 'existing-cursor-token' }); expect(mockSpiceClient.lookupResources).toHaveBeenCalledWith( diff --git a/src/spicedb/spicedb-entitlements.client.ts b/src/spicedb/spicedb-entitlements.client.ts index 63906a6..5ccebbe 100644 --- a/src/spicedb/spicedb-entitlements.client.ts +++ b/src/spicedb/spicedb-entitlements.client.ts @@ -1,4 +1,5 @@ import { ClientConfiguration, FallbackConfiguration, StaticFallbackConfiguration } from '../client-configuration'; +import { DEFAULT_LOOKUP_LIMIT } from './lookup.constants'; import { EntitlementsResult, EntityEntitlementsContext, @@ -61,15 +62,14 @@ export class SpiceDBEntitlementsClient { public async lookupResources(req: LookupResourcesRequest): Promise { try { - const DEFAULT_LIMIT = 50; - const limit = req.options?.limit || DEFAULT_LIMIT; + const limit = req.limit ? req.limit : DEFAULT_LOOKUP_LIMIT; const request = buildLookupResourcesRequest({ subjectType: req.subjectType, subjectId: req.subjectId, resourceType: req.resourceType, permission: req.permission, limit, - cursor: req.options?.cursor + cursor: req.cursor }); const results = await this.spiceClient.lookupResources(request); diff --git a/src/spicedb/spicedb-queries/lookup-response.mapper.ts b/src/spicedb/spicedb-queries/lookup-response.mapper.ts index ddfd8dc..7b23879 100644 --- a/src/spicedb/spicedb-queries/lookup-response.mapper.ts +++ b/src/spicedb/spicedb-queries/lookup-response.mapper.ts @@ -6,18 +6,10 @@ import { LookupSubjectsResponse, Permissionship } from '../../types'; +import { permissionshipMap } from '../lookup.constants'; export function mapPermissionship(permissionship: v1.LookupPermissionship): Permissionship | undefined { - switch (permissionship) { - case v1.LookupPermissionship.HAS_PERMISSION: - return 'HAS_PERMISSION'; - case v1.LookupPermissionship.CONDITIONAL_PERMISSION: - return 'CONDITIONAL_PERMISSION'; - case v1.LookupPermissionship.UNSPECIFIED: - return undefined; - default: - return undefined; - } + return permissionshipMap.get(permissionship); } export function mapLookupResourcesResponse( diff --git a/src/types/lookup.types.ts b/src/types/lookup.types.ts index 3e4be23..a641a55 100644 --- a/src/types/lookup.types.ts +++ b/src/types/lookup.types.ts @@ -1,13 +1,7 @@ export type Permissionship = 'HAS_PERMISSION' | 'CONDITIONAL_PERMISSION' | 'NO_PERMISSION'; -export interface LookupOptions { - limit?: number; - cursor?: string; -} - export interface LookupRequest { permission: string; - options?: LookupOptions; } export interface LookupResponse { @@ -19,6 +13,8 @@ export interface LookupResourcesRequest extends LookupRequest { subjectType: string; subjectId: string; resourceType: string; + limit?: number; + cursor?: string; } export interface LookupResourceItem { From 62ab7be0f029b647c6518aa79145a07d6d4a0790 Mon Sep 17 00:00:00 2001 From: Niv Date: Wed, 24 Dec 2025 15:25:42 +0200 Subject: [PATCH 09/16] corrected lookups responses --- src/types/lookup.types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/lookup.types.ts b/src/types/lookup.types.ts index a641a55..89b2c80 100644 --- a/src/types/lookup.types.ts +++ b/src/types/lookup.types.ts @@ -5,7 +5,6 @@ export interface LookupRequest { } export interface LookupResponse { - cursor?: string; totalReturned: number; } @@ -25,6 +24,7 @@ export interface LookupResourceItem { export interface LookupResourcesResponse extends LookupResponse { resources: LookupResourceItem[]; + cursor?: string; } export interface LookupSubjectsRequest extends LookupRequest { From d52702c30fbda7544cafee5f2897f076baa7d319 Mon Sep 17 00:00:00 2001 From: Niv Date: Wed, 24 Dec 2025 15:58:33 +0200 Subject: [PATCH 10/16] replace mapPermissionship function with direct access to permissionshipMap --- .../spicedb-queries/lookup-response.mapper.spec.ts | 13 ++++++++----- .../spicedb-queries/lookup-response.mapper.ts | 8 ++------ 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/spicedb/spicedb-queries/lookup-response.mapper.spec.ts b/src/spicedb/spicedb-queries/lookup-response.mapper.spec.ts index 91ff321..192d14b 100644 --- a/src/spicedb/spicedb-queries/lookup-response.mapper.spec.ts +++ b/src/spicedb/spicedb-queries/lookup-response.mapper.spec.ts @@ -1,22 +1,25 @@ import { v1 } from '@authzed/authzed-node'; -import { mapLookupResourcesResponse, mapLookupSubjectsResponse, mapPermissionship } from './lookup-response.mapper'; +import { mapLookupResourcesResponse, mapLookupSubjectsResponse } from './lookup-response.mapper'; +import { permissionshipMap } from '../lookup.constants'; describe('lookup-response.mapper', () => { describe('mapPermissionship', () => { it('should map HAS_PERMISSION', () => { - expect(mapPermissionship(v1.LookupPermissionship.HAS_PERMISSION)).toBe('HAS_PERMISSION'); + expect(permissionshipMap.get(v1.LookupPermissionship.HAS_PERMISSION)).toBe('HAS_PERMISSION'); }); it('should map CONDITIONAL_PERMISSION', () => { - expect(mapPermissionship(v1.LookupPermissionship.CONDITIONAL_PERMISSION)).toBe('CONDITIONAL_PERMISSION'); + expect(permissionshipMap.get(v1.LookupPermissionship.CONDITIONAL_PERMISSION)).toBe( + 'CONDITIONAL_PERMISSION' + ); }); it('should map UNSPECIFIED to undefined', () => { - expect(mapPermissionship(v1.LookupPermissionship.UNSPECIFIED)).toBeUndefined(); + expect(permissionshipMap.get(v1.LookupPermissionship.UNSPECIFIED)).toBeUndefined(); }); it('should map unknown values to undefined', () => { - expect(mapPermissionship(999 as v1.LookupPermissionship)).toBeUndefined(); + expect(permissionshipMap.get(999 as v1.LookupPermissionship)).toBeUndefined(); }); }); diff --git a/src/spicedb/spicedb-queries/lookup-response.mapper.ts b/src/spicedb/spicedb-queries/lookup-response.mapper.ts index 7b23879..e71f4c0 100644 --- a/src/spicedb/spicedb-queries/lookup-response.mapper.ts +++ b/src/spicedb/spicedb-queries/lookup-response.mapper.ts @@ -8,10 +8,6 @@ import { } from '../../types'; import { permissionshipMap } from '../lookup.constants'; -export function mapPermissionship(permissionship: v1.LookupPermissionship): Permissionship | undefined { - return permissionshipMap.get(permissionship); -} - export function mapLookupResourcesResponse( results: v1.LookupResourcesResponse[], resourceType: string, @@ -20,7 +16,7 @@ export function mapLookupResourcesResponse( const resources: LookupResourceItem[] = results.map((result) => ({ resourceType, resourceId: result.resourceObjectId, - permissionship: mapPermissionship(result.permissionship) + permissionship: permissionshipMap.get(result.permissionship) })); const lastResult = results.length > 0 ? results[results.length - 1] : undefined; @@ -40,7 +36,7 @@ export function mapLookupSubjectsResponse( const subjects: LookupSubjectItem[] = results.map((result) => ({ subjectType, subjectId: result.subject?.subjectObjectId ?? '', - permissionship: result.subject ? mapPermissionship(result.subject.permissionship) : undefined + permissionship: result.subject ? permissionshipMap.get(result.subject.permissionship) : undefined })); return { From d0919faf553105d2649c97acc0b33ff109a12e9f Mon Sep 17 00:00:00 2001 From: Niv Date: Wed, 24 Dec 2025 16:01:15 +0200 Subject: [PATCH 11/16] added as const --- src/spicedb/lookup.constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spicedb/lookup.constants.ts b/src/spicedb/lookup.constants.ts index 8c26d24..b366ac6 100644 --- a/src/spicedb/lookup.constants.ts +++ b/src/spicedb/lookup.constants.ts @@ -6,4 +6,4 @@ export const DEFAULT_LOOKUP_LIMIT = 50; export const permissionshipMap = new Map([ [v1.LookupPermissionship.HAS_PERMISSION, 'HAS_PERMISSION'], [v1.LookupPermissionship.CONDITIONAL_PERMISSION, 'CONDITIONAL_PERMISSION'] -]); +] as const); From 6050b600f95fa6d33bf902e6404cab9d327a4e7b Mon Sep 17 00:00:00 2001 From: Niv Date: Wed, 24 Dec 2025 16:18:04 +0200 Subject: [PATCH 12/16] fxied intefaces --- .../spicedb-queries/lookup-request.builder.ts | 6 +-- src/types/lookup.types.ts | 48 ++++++------------- 2 files changed, 18 insertions(+), 36 deletions(-) diff --git a/src/spicedb/spicedb-queries/lookup-request.builder.ts b/src/spicedb/spicedb-queries/lookup-request.builder.ts index 8cce2cc..468151b 100644 --- a/src/spicedb/spicedb-queries/lookup-request.builder.ts +++ b/src/spicedb/spicedb-queries/lookup-request.builder.ts @@ -1,7 +1,7 @@ import { v1 } from '@authzed/authzed-node'; -import { LookupResourcesParams, LookupSubjectsParams } from '../../types/lookup.types'; +import { LookupResourcesRequest, LookupSubjectsRequest } from '../../types/lookup.types'; -export function buildLookupResourcesRequest(params: LookupResourcesParams): v1.LookupResourcesRequest { +export function buildLookupResourcesRequest(params: LookupResourcesRequest): v1.LookupResourcesRequest { const { subjectType, subjectId, resourceType, permission, limit, cursor } = params; return v1.LookupResourcesRequest.create({ @@ -19,7 +19,7 @@ export function buildLookupResourcesRequest(params: LookupResourcesParams): v1.L }); } -export function buildLookupSubjectsRequest(params: LookupSubjectsParams): v1.LookupSubjectsRequest { +export function buildLookupSubjectsRequest(params: LookupSubjectsRequest): v1.LookupSubjectsRequest { const { resourceType, resourceId, subjectType, permission } = params; return v1.LookupSubjectsRequest.create({ diff --git a/src/types/lookup.types.ts b/src/types/lookup.types.ts index 89b2c80..1373312 100644 --- a/src/types/lookup.types.ts +++ b/src/types/lookup.types.ts @@ -1,60 +1,42 @@ export type Permissionship = 'HAS_PERMISSION' | 'CONDITIONAL_PERMISSION' | 'NO_PERMISSION'; -export interface LookupRequest { +export interface LookupBaseRequest { permission: string; + resourceType: string; + subjectType: string; } -export interface LookupResponse { +export interface LookupBaseResponse { totalReturned: number; } -export interface LookupResourcesRequest extends LookupRequest { - subjectType: string; +export interface LookupBaseItem { + permissionship?: Permissionship; +} +export interface LookupResourcesRequest extends LookupBaseRequest { subjectId: string; - resourceType: string; limit?: number; cursor?: string; } -export interface LookupResourceItem { +export interface LookupResourceItem extends LookupBaseItem { resourceType: string; resourceId: string; - permissionship?: Permissionship; } -export interface LookupResourcesResponse extends LookupResponse { +export interface LookupResourcesResponse extends LookupBaseResponse { resources: LookupResourceItem[]; cursor?: string; } -export interface LookupSubjectsRequest extends LookupRequest { - resourceType: string; - resourceId: string; - subjectType: string; -} - -export interface LookupSubjectItem { +export interface LookupSubjectItem extends LookupBaseItem { subjectType: string; subjectId: string; - permissionship?: Permissionship; -} - -export interface LookupSubjectsResponse extends LookupResponse { - subjects: LookupSubjectItem[]; } - -export interface LookupResourcesParams { - subjectType: string; - subjectId: string; - resourceType: string; - permission: string; - limit: number; - cursor?: string; +export interface LookupSubjectsRequest extends LookupBaseRequest { + resourceId: string; } -export interface LookupSubjectsParams { - resourceType: string; - resourceId: string; - subjectType: string; - permission: string; +export interface LookupSubjectsResponse extends LookupBaseResponse { + subjects: LookupSubjectItem[]; } From 71944835cc74ec41286bee46b0eedff61b67c3b3 Mon Sep 17 00:00:00 2001 From: Niv Date: Thu, 25 Dec 2025 10:50:29 +0200 Subject: [PATCH 13/16] added a shared encode/decode base64 util, encode requests subject/resource and decode response --- ...titlements.client.lookup-resources.spec.ts | 31 ++--- ...ntitlements.client.lookup-subjects.spec.ts | 44 +++---- .../spicedb-queries/base64.utils.spec.ts | 114 ++++++++++++++++++ src/spicedb/spicedb-queries/base64.utils.ts | 9 ++ .../entitlements-spicedb.query.spec.ts | 27 ++--- .../entitlements-spicedb.query.ts | 14 +-- .../spicedb-queries/fga-spicedb.query.ts | 5 +- .../lookup-request.builder.spec.ts | 46 ++++++- .../spicedb-queries/lookup-request.builder.ts | 5 +- .../lookup-response.mapper.spec.ts | 73 +++++++++-- .../spicedb-queries/lookup-response.mapper.ts | 5 +- .../spicedb-queries/route-spicedb.query.ts | 3 +- 12 files changed, 290 insertions(+), 86 deletions(-) create mode 100644 src/spicedb/spicedb-queries/base64.utils.spec.ts create mode 100644 src/spicedb/spicedb-queries/base64.utils.ts diff --git a/src/spicedb/spicedb-entitlements.client.lookup-resources.spec.ts b/src/spicedb/spicedb-entitlements.client.lookup-resources.spec.ts index 8ff0f32..dc671a2 100644 --- a/src/spicedb/spicedb-entitlements.client.lookup-resources.spec.ts +++ b/src/spicedb/spicedb-entitlements.client.lookup-resources.spec.ts @@ -4,6 +4,7 @@ import { LoggingClient } from '../logging'; import { ClientConfiguration } from '../client-configuration'; import { v1 } from '@authzed/authzed-node'; import { LookupResourcesRequest } from '../types'; +import { encodeObjectId } from './spicedb-queries/base64.utils'; describe('SpiceDBEntitlementsClient.lookupResources', () => { let mockSpiceClient: MockProxy; @@ -31,18 +32,19 @@ describe('SpiceDBEntitlementsClient.lookupResources', () => { }); describe('successful lookups', () => { - it('should return resources with correct structure', async () => { + it('should return resources with correct structure (decoded from base64)', async () => { + // SpiceDB returns base64-encoded IDs const mockResults: v1.LookupResourcesResponse[] = [ { lookedUpAt: undefined, - resourceObjectId: 'resource-1', + resourceObjectId: encodeObjectId('resource-1'), permissionship: v1.LookupPermissionship.HAS_PERMISSION, partialCaveatInfo: undefined, afterResultCursor: undefined }, { lookedUpAt: undefined, - resourceObjectId: 'resource-2', + resourceObjectId: encodeObjectId('resource-2'), permissionship: v1.LookupPermissionship.CONDITIONAL_PERMISSION, partialCaveatInfo: undefined, afterResultCursor: undefined @@ -53,6 +55,7 @@ describe('SpiceDBEntitlementsClient.lookupResources', () => { const result = await client.lookupResources(defaultRequest); expect(result.resources).toHaveLength(2); + // Response should contain decoded IDs expect(result.resources[0]).toEqual({ resourceType: 'document', resourceId: 'resource-1', @@ -101,7 +104,7 @@ describe('SpiceDBEntitlementsClient.lookupResources', () => { ); }); - it('should build correct SpiceDB request', async () => { + it('should build correct SpiceDB request with base64-encoded subjectId', async () => { mockSpiceClient.lookupResources.mockResolvedValue([]); await client.lookupResources(defaultRequest); @@ -113,7 +116,7 @@ describe('SpiceDBEntitlementsClient.lookupResources', () => { subject: expect.objectContaining({ object: expect.objectContaining({ objectType: 'user', - objectId: 'user-123' + objectId: encodeObjectId('user-123') // Request should encode the ID }), optionalRelation: '' }) @@ -127,7 +130,7 @@ describe('SpiceDBEntitlementsClient.lookupResources', () => { // Create 50 mock results (the default limit) with a cursor const mockResults: v1.LookupResourcesResponse[] = Array.from({ length: 50 }, (_, i) => ({ lookedUpAt: undefined, - resourceObjectId: `resource-${i + 1}`, + resourceObjectId: encodeObjectId(`resource-${i + 1}`), permissionship: v1.LookupPermissionship.HAS_PERMISSION, partialCaveatInfo: undefined, afterResultCursor: i === 49 ? { token: 'next-page-token' } : undefined @@ -144,7 +147,7 @@ describe('SpiceDBEntitlementsClient.lookupResources', () => { const mockResults: v1.LookupResourcesResponse[] = [ { lookedUpAt: undefined, - resourceObjectId: 'resource-1', + resourceObjectId: encodeObjectId('resource-1'), permissionship: v1.LookupPermissionship.HAS_PERMISSION, partialCaveatInfo: undefined, afterResultCursor: { token: 'next-page-token' } @@ -162,7 +165,7 @@ describe('SpiceDBEntitlementsClient.lookupResources', () => { const mockResults: v1.LookupResourcesResponse[] = [ { lookedUpAt: undefined, - resourceObjectId: 'resource-1', + resourceObjectId: encodeObjectId('resource-1'), permissionship: v1.LookupPermissionship.HAS_PERMISSION, partialCaveatInfo: undefined, afterResultCursor: undefined // No cursor @@ -208,7 +211,7 @@ describe('SpiceDBEntitlementsClient.lookupResources', () => { const mockResults: v1.LookupResourcesResponse[] = [ { lookedUpAt: undefined, - resourceObjectId: 'resource-1', + resourceObjectId: encodeObjectId('resource-1'), permissionship: v1.LookupPermissionship.HAS_PERMISSION, partialCaveatInfo: undefined, afterResultCursor: undefined @@ -225,7 +228,7 @@ describe('SpiceDBEntitlementsClient.lookupResources', () => { const mockResults: v1.LookupResourcesResponse[] = [ { lookedUpAt: undefined, - resourceObjectId: 'resource-1', + resourceObjectId: encodeObjectId('resource-1'), permissionship: v1.LookupPermissionship.CONDITIONAL_PERMISSION, partialCaveatInfo: undefined, afterResultCursor: undefined @@ -242,7 +245,7 @@ describe('SpiceDBEntitlementsClient.lookupResources', () => { const mockResults: v1.LookupResourcesResponse[] = [ { lookedUpAt: undefined, - resourceObjectId: 'resource-1', + resourceObjectId: encodeObjectId('resource-1'), permissionship: v1.LookupPermissionship.UNSPECIFIED, partialCaveatInfo: undefined, afterResultCursor: undefined @@ -270,21 +273,21 @@ describe('SpiceDBEntitlementsClient.lookupResources', () => { const mockResults: v1.LookupResourcesResponse[] = [ { lookedUpAt: undefined, - resourceObjectId: 'first', + resourceObjectId: encodeObjectId('first'), permissionship: v1.LookupPermissionship.HAS_PERMISSION, partialCaveatInfo: undefined, afterResultCursor: undefined }, { lookedUpAt: undefined, - resourceObjectId: 'second', + resourceObjectId: encodeObjectId('second'), permissionship: v1.LookupPermissionship.HAS_PERMISSION, partialCaveatInfo: undefined, afterResultCursor: undefined }, { lookedUpAt: undefined, - resourceObjectId: 'third', + resourceObjectId: encodeObjectId('third'), permissionship: v1.LookupPermissionship.HAS_PERMISSION, partialCaveatInfo: undefined, afterResultCursor: undefined diff --git a/src/spicedb/spicedb-entitlements.client.lookup-subjects.spec.ts b/src/spicedb/spicedb-entitlements.client.lookup-subjects.spec.ts index 5921784..6f6a2a6 100644 --- a/src/spicedb/spicedb-entitlements.client.lookup-subjects.spec.ts +++ b/src/spicedb/spicedb-entitlements.client.lookup-subjects.spec.ts @@ -4,6 +4,7 @@ import { LoggingClient } from '../logging'; import { ClientConfiguration } from '../client-configuration'; import { v1 } from '@authzed/authzed-node'; import { LookupSubjectsRequest } from '../types'; +import { encodeObjectId } from './spicedb-queries/base64.utils'; describe('SpiceDBEntitlementsClient.lookupSubjects', () => { let mockSpiceClient: MockProxy; @@ -31,16 +32,17 @@ describe('SpiceDBEntitlementsClient.lookupSubjects', () => { }); describe('successful lookups', () => { - it('should return subjects with correct structure', async () => { + it('should return subjects with correct structure (decoded from base64)', async () => { + // SpiceDB returns base64-encoded IDs const mockResults: v1.LookupSubjectsResponse[] = [ { lookedUpAt: undefined, - subjectObjectId: 'user-1', + subjectObjectId: encodeObjectId('user-1'), excludedSubjectIds: [], permissionship: v1.LookupPermissionship.HAS_PERMISSION, partialCaveatInfo: undefined, subject: { - subjectObjectId: 'user-1', + subjectObjectId: encodeObjectId('user-1'), permissionship: v1.LookupPermissionship.HAS_PERMISSION, partialCaveatInfo: undefined }, @@ -49,12 +51,12 @@ describe('SpiceDBEntitlementsClient.lookupSubjects', () => { }, { lookedUpAt: undefined, - subjectObjectId: 'user-2', + subjectObjectId: encodeObjectId('user-2'), excludedSubjectIds: [], permissionship: v1.LookupPermissionship.CONDITIONAL_PERMISSION, partialCaveatInfo: undefined, subject: { - subjectObjectId: 'user-2', + subjectObjectId: encodeObjectId('user-2'), permissionship: v1.LookupPermissionship.CONDITIONAL_PERMISSION, partialCaveatInfo: undefined }, @@ -67,6 +69,7 @@ describe('SpiceDBEntitlementsClient.lookupSubjects', () => { const result = await client.lookupSubjects(defaultRequest); expect(result.subjects).toHaveLength(2); + // Response should contain decoded IDs expect(result.subjects[0]).toEqual({ subjectType: 'user', subjectId: 'user-1', @@ -89,7 +92,7 @@ describe('SpiceDBEntitlementsClient.lookupSubjects', () => { expect(result.totalReturned).toBe(0); }); - it('should build correct SpiceDB request', async () => { + it('should build correct SpiceDB request with base64-encoded resourceId', async () => { mockSpiceClient.lookupSubjects.mockResolvedValue([]); await client.lookupSubjects(defaultRequest); @@ -98,7 +101,7 @@ describe('SpiceDBEntitlementsClient.lookupSubjects', () => { expect.objectContaining({ resource: expect.objectContaining({ objectType: 'document', - objectId: 'doc-123' + objectId: encodeObjectId('doc-123') // Request should encode the ID }), permission: 'view', subjectObjectType: 'user' @@ -112,12 +115,12 @@ describe('SpiceDBEntitlementsClient.lookupSubjects', () => { const mockResults: v1.LookupSubjectsResponse[] = [ { lookedUpAt: undefined, - subjectObjectId: 'user-1', + subjectObjectId: encodeObjectId('user-1'), excludedSubjectIds: [], permissionship: v1.LookupPermissionship.HAS_PERMISSION, partialCaveatInfo: undefined, subject: { - subjectObjectId: 'user-1', + subjectObjectId: encodeObjectId('user-1'), permissionship: v1.LookupPermissionship.HAS_PERMISSION, partialCaveatInfo: undefined }, @@ -136,12 +139,12 @@ describe('SpiceDBEntitlementsClient.lookupSubjects', () => { const mockResults: v1.LookupSubjectsResponse[] = [ { lookedUpAt: undefined, - subjectObjectId: 'user-1', + subjectObjectId: encodeObjectId('user-1'), excludedSubjectIds: [], permissionship: v1.LookupPermissionship.CONDITIONAL_PERMISSION, partialCaveatInfo: undefined, subject: { - subjectObjectId: 'user-1', + subjectObjectId: encodeObjectId('user-1'), permissionship: v1.LookupPermissionship.CONDITIONAL_PERMISSION, partialCaveatInfo: undefined }, @@ -160,12 +163,12 @@ describe('SpiceDBEntitlementsClient.lookupSubjects', () => { const mockResults: v1.LookupSubjectsResponse[] = [ { lookedUpAt: undefined, - subjectObjectId: 'user-1', + subjectObjectId: encodeObjectId('user-1'), excludedSubjectIds: [], permissionship: v1.LookupPermissionship.UNSPECIFIED, partialCaveatInfo: undefined, subject: { - subjectObjectId: 'user-1', + subjectObjectId: encodeObjectId('user-1'), permissionship: v1.LookupPermissionship.UNSPECIFIED, partialCaveatInfo: undefined }, @@ -184,7 +187,7 @@ describe('SpiceDBEntitlementsClient.lookupSubjects', () => { const mockResults: v1.LookupSubjectsResponse[] = [ { lookedUpAt: undefined, - subjectObjectId: 'user-1', + subjectObjectId: encodeObjectId('user-1'), excludedSubjectIds: [], permissionship: v1.LookupPermissionship.HAS_PERMISSION, partialCaveatInfo: undefined, @@ -225,12 +228,12 @@ describe('SpiceDBEntitlementsClient.lookupSubjects', () => { const mockResults: v1.LookupSubjectsResponse[] = [ { lookedUpAt: undefined, - subjectObjectId: 'first', + subjectObjectId: encodeObjectId('first'), excludedSubjectIds: [], permissionship: v1.LookupPermissionship.HAS_PERMISSION, partialCaveatInfo: undefined, subject: { - subjectObjectId: 'first', + subjectObjectId: encodeObjectId('first'), permissionship: v1.LookupPermissionship.HAS_PERMISSION, partialCaveatInfo: undefined }, @@ -239,12 +242,12 @@ describe('SpiceDBEntitlementsClient.lookupSubjects', () => { }, { lookedUpAt: undefined, - subjectObjectId: 'second', + subjectObjectId: encodeObjectId('second'), excludedSubjectIds: [], permissionship: v1.LookupPermissionship.HAS_PERMISSION, partialCaveatInfo: undefined, subject: { - subjectObjectId: 'second', + subjectObjectId: encodeObjectId('second'), permissionship: v1.LookupPermissionship.HAS_PERMISSION, partialCaveatInfo: undefined }, @@ -253,12 +256,12 @@ describe('SpiceDBEntitlementsClient.lookupSubjects', () => { }, { lookedUpAt: undefined, - subjectObjectId: 'third', + subjectObjectId: encodeObjectId('third'), excludedSubjectIds: [], permissionship: v1.LookupPermissionship.HAS_PERMISSION, partialCaveatInfo: undefined, subject: { - subjectObjectId: 'third', + subjectObjectId: encodeObjectId('third'), permissionship: v1.LookupPermissionship.HAS_PERMISSION, partialCaveatInfo: undefined }, @@ -297,4 +300,3 @@ describe('SpiceDBEntitlementsClient.lookupSubjects', () => { }); }); }); - diff --git a/src/spicedb/spicedb-queries/base64.utils.spec.ts b/src/spicedb/spicedb-queries/base64.utils.spec.ts new file mode 100644 index 0000000..5120b0f --- /dev/null +++ b/src/spicedb/spicedb-queries/base64.utils.spec.ts @@ -0,0 +1,114 @@ +import { encodeObjectId, decodeObjectId } from './base64.utils'; + +describe('base64.utils', () => { + describe('encodeObjectId', () => { + it('should encode simple string to base64', () => { + const result = encodeObjectId('user-123'); + expect(result).toBe('dXNlci0xMjM'); + }); + + it('should produce URL-safe base64 (no +, /, or =)', () => { + // This string when base64 encoded would normally contain + and / + const result = encodeObjectId('test+data/with=special'); + expect(result).not.toContain('+'); + expect(result).not.toContain('/'); + expect(result).not.toContain('='); + }); + + it('should replace + with -', () => { + // Force a scenario that produces + in base64 + const input = '>>>'; // base64: Pj4+ (contains +) + const result = encodeObjectId(input); + expect(result).not.toContain('+'); + expect(result).toContain('-'); // + should be replaced with - + }); + + it('should replace / with _', () => { + // Force a scenario that produces / in base64 + const input = '???'; // base64: Pz8/ (contains /) + const result = encodeObjectId(input); + expect(result).not.toContain('/'); + expect(result).toContain('_'); // / should be replaced with _ + }); + + it('should handle empty string', () => { + const result = encodeObjectId(''); + expect(result).toBe(''); + }); + + it('should handle real customer data IDs', () => { + const result = encodeObjectId('user-1-DWdTPCoH'); + expect(typeof result).toBe('string'); + expect(result.length).toBeGreaterThan(0); + }); + }); + + describe('decodeObjectId', () => { + it('should decode base64 to original string', () => { + const encoded = encodeObjectId('user-123'); + const decoded = decodeObjectId(encoded); + expect(decoded).toBe('user-123'); + }); + + it('should handle URL-safe base64 characters', () => { + // Encode something that produces + and / + const original = '>>>'; + const encoded = encodeObjectId(original); + const decoded = decodeObjectId(encoded); + expect(decoded).toBe(original); + }); + + it('should roundtrip any string correctly', () => { + const testCases = [ + 'user-1-DWdTPCoH', + 'doc-1-aOD3DYIF', + 'group-15-2FyCyAMR', + 'sub-100-XcpmSEUD', + 'test+special/chars=here', + 'unicode: 日本語' + ]; + + for (const original of testCases) { + const encoded = encodeObjectId(original); + const decoded = decodeObjectId(encoded); + expect(decoded).toBe(original); + } + }); + + it('should handle empty string', () => { + const result = decodeObjectId(''); + expect(result).toBe(''); + }); + + it('should handle base64 strings from SpiceDB export', () => { + // Real example from the yaml: Z3JvdXAtMS02MWRrNlRpYw = group-1-61dk6Tic + const decoded = decodeObjectId('Z3JvdXAtMS02MWRrNlRpYw'); + expect(decoded).toBe('group-1-61dk6Tic'); + }); + + it('should handle base64 user IDs from SpiceDB export', () => { + // Real example: dXNlci0xLURXZFRQQ29I = user-1-DWdTPCoH + const decoded = decodeObjectId('dXNlci0xLURXZFRQQ29I'); + expect(decoded).toBe('user-1-DWdTPCoH'); + }); + }); + + describe('encode/decode symmetry', () => { + it('should decode what encode produces', () => { + const originals = [ + 'simple', + 'with-dashes', + 'with_underscores', + 'with.dots', + 'CamelCase', + '123456', + 'mixed-123_test.case' + ]; + + for (const original of originals) { + expect(decodeObjectId(encodeObjectId(original))).toBe(original); + } + }); + }); +}); + diff --git a/src/spicedb/spicedb-queries/base64.utils.ts b/src/spicedb/spicedb-queries/base64.utils.ts new file mode 100644 index 0000000..e350440 --- /dev/null +++ b/src/spicedb/spicedb-queries/base64.utils.ts @@ -0,0 +1,9 @@ +export function encodeObjectId(objectId: string): string { + return Buffer.from(objectId).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); +} + +export function decodeObjectId(encodedId: string): string { + const base64 = encodedId.replace(/-/g, '+').replace(/_/g, '/'); + const paddedBase64 = base64 + '='.repeat((4 - (base64.length % 4)) % 4); + return Buffer.from(paddedBase64, 'base64').toString('utf-8'); +} diff --git a/src/spicedb/spicedb-queries/entitlements-spicedb.query.spec.ts b/src/spicedb/spicedb-queries/entitlements-spicedb.query.spec.ts index bc89f11..adcdc8d 100644 --- a/src/spicedb/spicedb-queries/entitlements-spicedb.query.spec.ts +++ b/src/spicedb/spicedb-queries/entitlements-spicedb.query.spec.ts @@ -3,6 +3,7 @@ import { mock, MockProxy, mockReset } from 'jest-mock-extended'; import { EntitlementsSpiceDBQuery } from './entitlements-spicedb.query'; import { EntitlementsDynamicQuery, EntitlementsResult, RequestContextType, UserSubjectContext } from '../../types'; import { SpiceDBResponse } from '../../types/spicedb.dto'; +import { encodeObjectId } from './base64.utils'; // Create a concrete test implementation to test the abstract class class TestEntitlementsSpiceDBQuery extends EntitlementsSpiceDBQuery { @@ -58,10 +59,6 @@ class TestEntitlementsSpiceDBQuery extends EntitlementsSpiceDBQuery { ): Promise> { return this.executeCommonQuery(objectType, objectId, subjectContext); } - - public testNormalizeObjectId(objectId: string): string { - return this.normalizeObjectId(objectId); - } } describe(EntitlementsSpiceDBQuery.name, () => { @@ -187,10 +184,10 @@ describe(EntitlementsSpiceDBQuery.name, () => { ); expect(requestItem.resource?.objectType).toBe('frontegg_feature'); - expect(requestItem.resource?.objectId).toBe(queryClient.testNormalizeObjectId('test-feature')); + expect(requestItem.resource?.objectId).toBe(encodeObjectId('test-feature')); expect(requestItem.permission).toBe('access'); expect(requestItem.subject?.object?.objectType).toBe('frontegg_user'); - expect(requestItem.subject?.object?.objectId).toBe(queryClient.testNormalizeObjectId('user-123')); + expect(requestItem.subject?.object?.objectId).toBe(encodeObjectId('user-123')); expect(requestItem.subject?.optionalRelation).toBe(''); expect(requestItem.context).toBe(caveatContext); }); @@ -218,12 +215,12 @@ describe(EntitlementsSpiceDBQuery.name, () => { // Check tenant item const tenantItem = bulkRequest.items.find((item) => item.subject?.object?.objectType === 'frontegg_tenant'); expect(tenantItem).toBeDefined(); - expect(tenantItem?.subject?.object?.objectId).toBe(queryClient.testNormalizeObjectId('tenant-456')); + expect(tenantItem?.subject?.object?.objectId).toBe(encodeObjectId('tenant-456')); // Check user item const userItem = bulkRequest.items.find((item) => item.subject?.object?.objectType === 'frontegg_user'); expect(userItem).toBeDefined(); - expect(userItem?.subject?.object?.objectId).toBe(queryClient.testNormalizeObjectId('user-123')); + expect(userItem?.subject?.object?.objectId).toBe(encodeObjectId('user-123')); }); it('should create bulk permissions request with only tenant item when userId is undefined', () => { @@ -247,7 +244,7 @@ describe(EntitlementsSpiceDBQuery.name, () => { // Check tenant item const tenantItem = bulkRequest.items[0]; expect(tenantItem.subject?.object?.objectType).toBe('frontegg_tenant'); - expect(tenantItem.subject?.object?.objectId).toBe(queryClient.testNormalizeObjectId('tenant-456')); + expect(tenantItem.subject?.object?.objectId).toBe(encodeObjectId('tenant-456')); }); }); @@ -366,10 +363,10 @@ describe(EntitlementsSpiceDBQuery.name, () => { }); }); - describe('normalizeObjectId', () => { - it('should normalize object ID to base64 URL-safe format', () => { + describe('encode/decode objectId', () => { + it('should encode object ID to base64 URL-safe format', () => { const objectId = 'test-feature-123'; - const normalized = queryClient.testNormalizeObjectId(objectId); + const normalized = encodeObjectId(objectId); // Should be base64 encoded and URL-safe expect(normalized).not.toContain('+'); @@ -383,7 +380,7 @@ describe(EntitlementsSpiceDBQuery.name, () => { it('should handle special characters', () => { const objectId = 'test+feature/with=special/chars'; - const normalized = queryClient.testNormalizeObjectId(objectId); + const normalized = encodeObjectId(objectId); // Should be URL-safe expect(normalized).not.toContain('+'); @@ -393,14 +390,14 @@ describe(EntitlementsSpiceDBQuery.name, () => { it('should handle empty string', () => { const objectId = ''; - const normalized = queryClient.testNormalizeObjectId(objectId); + const normalized = encodeObjectId(objectId); expect(normalized).toBe(''); }); it('should handle unicode characters', () => { const objectId = 'test-功能-123'; - const normalized = queryClient.testNormalizeObjectId(objectId); + const normalized = encodeObjectId(objectId); // Should be URL-safe expect(normalized).not.toContain('+'); diff --git a/src/spicedb/spicedb-queries/entitlements-spicedb.query.ts b/src/spicedb/spicedb-queries/entitlements-spicedb.query.ts index 82ef2b4..1810c0a 100644 --- a/src/spicedb/spicedb-queries/entitlements-spicedb.query.ts +++ b/src/spicedb/spicedb-queries/entitlements-spicedb.query.ts @@ -8,6 +8,7 @@ import { } from '../../types'; import { SpiceDBResponse } from '../../types/spicedb.dto'; import { SpiceDBEntities } from '../../types/spicedb-consts'; +import { encodeObjectId } from './base64.utils'; export interface HashOptions { hashResourceId: boolean; @@ -65,13 +66,13 @@ export abstract class EntitlementsSpiceDBQuery { return { resource: { objectType: resourceObjectType, - objectId: hashOptions.hashResourceId ? this.normalizeObjectId(resourceObjectId) : resourceObjectId + objectId: hashOptions.hashResourceId ? encodeObjectId(resourceObjectId) : resourceObjectId }, permission: 'access', subject: { object: { objectType: subjectObjectType, - objectId: hashOptions.hashSubjectId ? this.normalizeObjectId(subjectObjectId) : subjectObjectId + objectId: hashOptions.hashSubjectId ? encodeObjectId(subjectObjectId) : subjectObjectId }, optionalRelation: '' }, @@ -134,11 +135,6 @@ export abstract class EntitlementsSpiceDBQuery { result: { result } as EntitlementsResult } as SpiceDBResponse; } - - protected normalizeObjectId(objectId: string): string { - return Buffer.from(objectId).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); - } - protected async isPermissionLinkedToFeatures( requestContext: PermissionsEntitlementsContext, hashResourceId: boolean = true @@ -147,9 +143,7 @@ export abstract class EntitlementsSpiceDBQuery { permission: 'parent', resource: { objectType: SpiceDBEntities.Permission, - objectId: hashResourceId - ? this.normalizeObjectId(requestContext.permissionKey) - : requestContext.permissionKey + objectId: hashResourceId ? encodeObjectId(requestContext.permissionKey) : requestContext.permissionKey }, subjectObjectType: SpiceDBEntities.Feature }); diff --git a/src/spicedb/spicedb-queries/fga-spicedb.query.ts b/src/spicedb/spicedb-queries/fga-spicedb.query.ts index 2941f2c..f8f2b5b 100644 --- a/src/spicedb/spicedb-queries/fga-spicedb.query.ts +++ b/src/spicedb/spicedb-queries/fga-spicedb.query.ts @@ -2,6 +2,7 @@ import { EntitlementsSpiceDBQuery } from './entitlements-spicedb.query'; import { EntitlementsDynamicQuery, EntitlementsResult, FGASubjectContext, RequestContextType } from '../../types'; import { SpiceDBResponse } from '../../types/spicedb.dto'; import { v1 } from '@authzed/authzed-node'; +import { encodeObjectId } from './base64.utils'; export class FgaSpiceDBQuery extends EntitlementsSpiceDBQuery { constructor(protected readonly client: v1.ZedPromiseClientInterface) { @@ -17,13 +18,13 @@ export class FgaSpiceDBQuery extends EntitlementsSpiceDBQuery { subject: { object: { objectType: context.entityType, - objectId: this.normalizeObjectId(context.key) + objectId: encodeObjectId(context.key) }, optionalRelation: '' }, resource: { objectType: requestContext.entityType, - objectId: this.normalizeObjectId(requestContext.key) + objectId: encodeObjectId(requestContext.key) }, permission: requestContext.action }); diff --git a/src/spicedb/spicedb-queries/lookup-request.builder.spec.ts b/src/spicedb/spicedb-queries/lookup-request.builder.spec.ts index b7db5eb..1df5e70 100644 --- a/src/spicedb/spicedb-queries/lookup-request.builder.spec.ts +++ b/src/spicedb/spicedb-queries/lookup-request.builder.spec.ts @@ -1,9 +1,10 @@ import { v1 } from '@authzed/authzed-node'; import { buildLookupResourcesRequest, buildLookupSubjectsRequest } from './lookup-request.builder'; +import { encodeObjectId } from './base64.utils'; describe('lookup-request.builder', () => { describe('buildLookupResourcesRequest', () => { - it('should build request with all parameters', () => { + it('should build request with all parameters and encode subjectId', () => { const result = buildLookupResourcesRequest({ subjectType: 'user', subjectId: 'user-123', @@ -16,7 +17,8 @@ describe('lookup-request.builder', () => { expect(result.resourceObjectType).toBe('document'); expect(result.permission).toBe('read'); expect(result.subject?.object?.objectType).toBe('user'); - expect(result.subject?.object?.objectId).toBe('user-123'); + // subjectId should be base64 encoded + expect(result.subject?.object?.objectId).toBe(encodeObjectId('user-123')); expect(result.subject?.optionalRelation).toBe(''); expect(result.optionalLimit).toBe(100); expect(result.optionalCursor?.token).toBe('cursor-token'); @@ -49,10 +51,26 @@ describe('lookup-request.builder', () => { expect(result).toHaveProperty('subject'); expect(result).toHaveProperty('optionalLimit'); }); + + it('should encode subjectId to URL-safe base64', () => { + const result = buildLookupResourcesRequest({ + subjectType: 'user', + subjectId: 'user+special/chars=test', + resourceType: 'document', + permission: 'read', + limit: 50 + }); + + const encodedId = result.subject?.object?.objectId; + // Should not contain +, /, or = (URL-safe base64) + expect(encodedId).not.toContain('+'); + expect(encodedId).not.toContain('/'); + expect(encodedId).not.toContain('='); + }); }); describe('buildLookupSubjectsRequest', () => { - it('should build request with all parameters', () => { + it('should build request with all parameters and encode resourceId', () => { const result = buildLookupSubjectsRequest({ resourceType: 'document', resourceId: 'doc-123', @@ -61,7 +79,8 @@ describe('lookup-request.builder', () => { }); expect(result.resource?.objectType).toBe('document'); - expect(result.resource?.objectId).toBe('doc-123'); + // resourceId should be base64 encoded + expect(result.resource?.objectId).toBe(encodeObjectId('doc-123')); expect(result.permission).toBe('view'); expect(result.subjectObjectType).toBe('user'); }); @@ -80,7 +99,7 @@ describe('lookup-request.builder', () => { expect(result).toHaveProperty('subjectObjectType'); }); - it('should handle different resource types', () => { + it('should handle different resource types and encode resourceId', () => { const result = buildLookupSubjectsRequest({ resourceType: 'folder', resourceId: 'folder-456', @@ -89,9 +108,24 @@ describe('lookup-request.builder', () => { }); expect(result.resource?.objectType).toBe('folder'); - expect(result.resource?.objectId).toBe('folder-456'); + expect(result.resource?.objectId).toBe(encodeObjectId('folder-456')); expect(result.subjectObjectType).toBe('group'); expect(result.permission).toBe('admin'); }); + + it('should encode resourceId to URL-safe base64', () => { + const result = buildLookupSubjectsRequest({ + resourceType: 'document', + resourceId: 'doc+special/chars=test', + subjectType: 'user', + permission: 'view' + }); + + const encodedId = result.resource?.objectId; + // Should not contain +, /, or = (URL-safe base64) + expect(encodedId).not.toContain('+'); + expect(encodedId).not.toContain('/'); + expect(encodedId).not.toContain('='); + }); }); }); diff --git a/src/spicedb/spicedb-queries/lookup-request.builder.ts b/src/spicedb/spicedb-queries/lookup-request.builder.ts index 468151b..e587d4b 100644 --- a/src/spicedb/spicedb-queries/lookup-request.builder.ts +++ b/src/spicedb/spicedb-queries/lookup-request.builder.ts @@ -1,5 +1,6 @@ import { v1 } from '@authzed/authzed-node'; import { LookupResourcesRequest, LookupSubjectsRequest } from '../../types/lookup.types'; +import { encodeObjectId } from './base64.utils'; export function buildLookupResourcesRequest(params: LookupResourcesRequest): v1.LookupResourcesRequest { const { subjectType, subjectId, resourceType, permission, limit, cursor } = params; @@ -10,7 +11,7 @@ export function buildLookupResourcesRequest(params: LookupResourcesRequest): v1. subject: { object: { objectType: subjectType, - objectId: subjectId + objectId: encodeObjectId(subjectId) }, optionalRelation: '' }, @@ -25,7 +26,7 @@ export function buildLookupSubjectsRequest(params: LookupSubjectsRequest): v1.Lo return v1.LookupSubjectsRequest.create({ resource: { objectType: resourceType, - objectId: resourceId + objectId: encodeObjectId(resourceId) }, permission: permission, subjectObjectType: subjectType diff --git a/src/spicedb/spicedb-queries/lookup-response.mapper.spec.ts b/src/spicedb/spicedb-queries/lookup-response.mapper.spec.ts index 192d14b..d59ceae 100644 --- a/src/spicedb/spicedb-queries/lookup-response.mapper.spec.ts +++ b/src/spicedb/spicedb-queries/lookup-response.mapper.spec.ts @@ -1,6 +1,7 @@ import { v1 } from '@authzed/authzed-node'; import { mapLookupResourcesResponse, mapLookupSubjectsResponse } from './lookup-response.mapper'; import { permissionshipMap } from '../lookup.constants'; +import { encodeObjectId } from './base64.utils'; describe('lookup-response.mapper', () => { describe('mapPermissionship', () => { @@ -24,18 +25,19 @@ describe('lookup-response.mapper', () => { }); describe('mapLookupResourcesResponse', () => { - it('should map resources correctly', () => { + it('should map resources correctly and decode base64 resourceIds', () => { + // SpiceDB returns base64 encoded IDs const results: v1.LookupResourcesResponse[] = [ { lookedUpAt: undefined, - resourceObjectId: 'resource-1', + resourceObjectId: encodeObjectId('resource-1'), permissionship: v1.LookupPermissionship.HAS_PERMISSION, partialCaveatInfo: undefined, afterResultCursor: undefined }, { lookedUpAt: undefined, - resourceObjectId: 'resource-2', + resourceObjectId: encodeObjectId('resource-2'), permissionship: v1.LookupPermissionship.CONDITIONAL_PERMISSION, partialCaveatInfo: undefined, afterResultCursor: undefined @@ -45,6 +47,7 @@ describe('lookup-response.mapper', () => { const response = mapLookupResourcesResponse(results, 'document', 50); expect(response.resources).toHaveLength(2); + // Should decode to original IDs expect(response.resources[0]).toEqual({ resourceType: 'document', resourceId: 'resource-1', @@ -61,7 +64,7 @@ describe('lookup-response.mapper', () => { it('should return cursor when results equal limit', () => { const results: v1.LookupResourcesResponse[] = Array.from({ length: 10 }, (_, i) => ({ lookedUpAt: undefined, - resourceObjectId: `resource-${i}`, + resourceObjectId: encodeObjectId(`resource-${i}`), permissionship: v1.LookupPermissionship.HAS_PERMISSION, partialCaveatInfo: undefined, afterResultCursor: i === 9 ? { token: 'next-cursor' } : undefined @@ -76,7 +79,7 @@ describe('lookup-response.mapper', () => { const results: v1.LookupResourcesResponse[] = [ { lookedUpAt: undefined, - resourceObjectId: 'resource-1', + resourceObjectId: encodeObjectId('resource-1'), permissionship: v1.LookupPermissionship.HAS_PERMISSION, partialCaveatInfo: undefined, afterResultCursor: { token: 'cursor-token' } @@ -95,19 +98,37 @@ describe('lookup-response.mapper', () => { expect(response.totalReturned).toBe(0); expect(response.cursor).toBeUndefined(); }); + + it('should decode base64 IDs with special characters correctly', () => { + const originalId = 'doc-1-aOD3DYIF'; + const results: v1.LookupResourcesResponse[] = [ + { + lookedUpAt: undefined, + resourceObjectId: encodeObjectId(originalId), + permissionship: v1.LookupPermissionship.HAS_PERMISSION, + partialCaveatInfo: undefined, + afterResultCursor: undefined + } + ]; + + const response = mapLookupResourcesResponse(results, 'document', 50); + + expect(response.resources[0].resourceId).toBe(originalId); + }); }); describe('mapLookupSubjectsResponse', () => { - it('should map subjects correctly', () => { + it('should map subjects correctly and decode base64 subjectIds', () => { + // SpiceDB returns base64 encoded IDs const results: v1.LookupSubjectsResponse[] = [ { lookedUpAt: undefined, - subjectObjectId: 'user-1', + subjectObjectId: encodeObjectId('user-1'), excludedSubjectIds: [], permissionship: v1.LookupPermissionship.HAS_PERMISSION, partialCaveatInfo: undefined, subject: { - subjectObjectId: 'user-1', + subjectObjectId: encodeObjectId('user-1'), permissionship: v1.LookupPermissionship.HAS_PERMISSION, partialCaveatInfo: undefined }, @@ -116,12 +137,12 @@ describe('lookup-response.mapper', () => { }, { lookedUpAt: undefined, - subjectObjectId: 'user-2', + subjectObjectId: encodeObjectId('user-2'), excludedSubjectIds: [], permissionship: v1.LookupPermissionship.CONDITIONAL_PERMISSION, partialCaveatInfo: undefined, subject: { - subjectObjectId: 'user-2', + subjectObjectId: encodeObjectId('user-2'), permissionship: v1.LookupPermissionship.CONDITIONAL_PERMISSION, partialCaveatInfo: undefined }, @@ -133,6 +154,7 @@ describe('lookup-response.mapper', () => { const response = mapLookupSubjectsResponse(results, 'user'); expect(response.subjects).toHaveLength(2); + // Should decode to original IDs expect(response.subjects[0]).toEqual({ subjectType: 'user', subjectId: 'user-1', @@ -157,7 +179,7 @@ describe('lookup-response.mapper', () => { const results: v1.LookupSubjectsResponse[] = [ { lookedUpAt: undefined, - subjectObjectId: 'user-1', + subjectObjectId: encodeObjectId('user-1'), excludedSubjectIds: [], permissionship: v1.LookupPermissionship.HAS_PERMISSION, partialCaveatInfo: undefined, @@ -177,14 +199,14 @@ describe('lookup-response.mapper', () => { const results: v1.LookupSubjectsResponse[] = [ { lookedUpAt: undefined, - subjectObjectId: 'user-1', + subjectObjectId: encodeObjectId('user-1'), excludedSubjectIds: [], // Deprecated top-level field says HAS_PERMISSION permissionship: v1.LookupPermissionship.HAS_PERMISSION, partialCaveatInfo: undefined, // But subject field says CONDITIONAL_PERMISSION subject: { - subjectObjectId: 'user-1', + subjectObjectId: encodeObjectId('user-1'), permissionship: v1.LookupPermissionship.CONDITIONAL_PERMISSION, partialCaveatInfo: undefined }, @@ -198,5 +220,30 @@ describe('lookup-response.mapper', () => { // Should use the subject field value, not the deprecated top-level one expect(response.subjects[0].permissionship).toBe('CONDITIONAL_PERMISSION'); }); + + it('should decode base64 IDs with real customer data format', () => { + // Simulating real data like: cust_user:dXNlci0xLURXZFRQQ29I + const originalUserId = 'user-1-DWdTPCoH'; + const results: v1.LookupSubjectsResponse[] = [ + { + lookedUpAt: undefined, + subjectObjectId: encodeObjectId(originalUserId), + excludedSubjectIds: [], + permissionship: v1.LookupPermissionship.HAS_PERMISSION, + partialCaveatInfo: undefined, + subject: { + subjectObjectId: encodeObjectId(originalUserId), + permissionship: v1.LookupPermissionship.HAS_PERMISSION, + partialCaveatInfo: undefined + }, + excludedSubjects: [], + afterResultCursor: undefined + } + ]; + + const response = mapLookupSubjectsResponse(results, 'cust_user'); + + expect(response.subjects[0].subjectId).toBe(originalUserId); + }); }); }); diff --git a/src/spicedb/spicedb-queries/lookup-response.mapper.ts b/src/spicedb/spicedb-queries/lookup-response.mapper.ts index e71f4c0..c64f306 100644 --- a/src/spicedb/spicedb-queries/lookup-response.mapper.ts +++ b/src/spicedb/spicedb-queries/lookup-response.mapper.ts @@ -7,6 +7,7 @@ import { Permissionship } from '../../types'; import { permissionshipMap } from '../lookup.constants'; +import { decodeObjectId } from './base64.utils'; export function mapLookupResourcesResponse( results: v1.LookupResourcesResponse[], @@ -15,7 +16,7 @@ export function mapLookupResourcesResponse( ): LookupResourcesResponse { const resources: LookupResourceItem[] = results.map((result) => ({ resourceType, - resourceId: result.resourceObjectId, + resourceId: decodeObjectId(result.resourceObjectId), permissionship: permissionshipMap.get(result.permissionship) })); @@ -35,7 +36,7 @@ export function mapLookupSubjectsResponse( ): LookupSubjectsResponse { const subjects: LookupSubjectItem[] = results.map((result) => ({ subjectType, - subjectId: result.subject?.subjectObjectId ?? '', + subjectId: decodeObjectId(result.subject?.subjectObjectId ?? ''), permissionship: result.subject ? permissionshipMap.get(result.subject.permissionship) : undefined })); diff --git a/src/spicedb/spicedb-queries/route-spicedb.query.ts b/src/spicedb/spicedb-queries/route-spicedb.query.ts index 52dd3d1..0a7fd6b 100644 --- a/src/spicedb/spicedb-queries/route-spicedb.query.ts +++ b/src/spicedb/spicedb-queries/route-spicedb.query.ts @@ -4,6 +4,7 @@ import { SpiceDBResponse } from '../../types/spicedb.dto'; import { v1 } from '@authzed/authzed-node'; import { LRUCache } from 'lru-cache'; import { SpiceDBEntities } from '../../types/spicedb-consts'; +import { encodeObjectId } from './base64.utils'; export class RouteSpiceDBQuery extends EntitlementsSpiceDBQuery { private readonly cache: LRUCache; @@ -98,7 +99,7 @@ export class RouteSpiceDBQuery extends EntitlementsSpiceDBQuery { firstRule = objects[0]; for (const rule of objects) { - const hashedPermissions = context.permissions?.map((permission) => this.normalizeObjectId(permission)); + const hashedPermissions = context.permissions?.map((permission) => encodeObjectId(permission)); if (rule.relation.includes('required_permission')) { if (!this.hasPermission(rule.subjectId, hashedPermissions)) { return this.createResult(false, isMonitoringEnabled); From c1ba13b5743acbdf5800c86bec59adabb0fb9a7c Mon Sep 17 00:00:00 2001 From: Niv Date: Thu, 25 Dec 2025 12:35:33 +0200 Subject: [PATCH 14/16] prettier --- src/spicedb/spicedb-queries/base64.utils.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/spicedb/spicedb-queries/base64.utils.spec.ts b/src/spicedb/spicedb-queries/base64.utils.spec.ts index 5120b0f..bee2abb 100644 --- a/src/spicedb/spicedb-queries/base64.utils.spec.ts +++ b/src/spicedb/spicedb-queries/base64.utils.spec.ts @@ -111,4 +111,3 @@ describe('base64.utils', () => { }); }); }); - From 3970702321e9726af2e8022253de321985fed2d0 Mon Sep 17 00:00:00 2001 From: Niv Date: Thu, 25 Dec 2025 12:39:21 +0200 Subject: [PATCH 15/16] fasfa --- src/spicedb/spicedb-queries/base64.utils.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/spicedb/spicedb-queries/base64.utils.spec.ts b/src/spicedb/spicedb-queries/base64.utils.spec.ts index bee2abb..5120b0f 100644 --- a/src/spicedb/spicedb-queries/base64.utils.spec.ts +++ b/src/spicedb/spicedb-queries/base64.utils.spec.ts @@ -111,3 +111,4 @@ describe('base64.utils', () => { }); }); }); + From f34b32004874dc6cad375455f1aa2cb30253fa51 Mon Sep 17 00:00:00 2001 From: Niv Date: Thu, 25 Dec 2025 12:40:14 +0200 Subject: [PATCH 16/16] prettier --- src/spicedb/spicedb-queries/base64.utils.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/spicedb/spicedb-queries/base64.utils.spec.ts b/src/spicedb/spicedb-queries/base64.utils.spec.ts index 5120b0f..bee2abb 100644 --- a/src/spicedb/spicedb-queries/base64.utils.spec.ts +++ b/src/spicedb/spicedb-queries/base64.utils.spec.ts @@ -111,4 +111,3 @@ describe('base64.utils', () => { }); }); }); -