diff --git a/README.md b/README.md index 2a12247..07b9a44 100644 --- a/README.md +++ b/README.md @@ -116,3 +116,26 @@ For a read-only scope M2M token, use: - API base configuration points to v6 in dev/local and v5 in prod (for backward compatibility): - Dev: work-manager/config/constants/development.js. - Local: work-manager/config/constants/local.js and work-manager/config/constants/local.js. + +## Shared authentication and bus client (v6 refresh) + +This branch uses `tc-core-library-js` **4.0.0-rc.1**. Use its named +`createJwtVerifier` / `createJwtAuthenticator`, `createM2MClient`, and +`createBusApiClient` exports for shared authentication and bus API publishing. +Service-specific roles, scopes, sessions and ownership checks remain in this service. +The former bus wrapper dependency and local JWT cryptography must not be reintroduced. + +RSA verification requires an exact configured issuer and audience (`JWT_AUDIENCE` +for client tokens, `AUTH0_AUDIENCE` for machine/API tokens, or this service's explicit +issuer-policy settings). HMAC secrets must be at least 32 bytes; no default secret +is supplied. Expiry is enforced. Machine identity comes from the shared verifier, +not the presence of scopes. Supply real development credentials through the service's +existing environment configuration before deployment. + +Bus publishers use `createBusApiClient(config).postEvent(event)` with the existing +event envelope and optional key. Configure `BUSAPI_URL` for v6 and the existing +OAuth credentials/token provider. The client uses bounded HTTPS requests and does +not retry POSTs automatically. Application retry/outbox behavior remains local. + +See the [core migration guide](https://github.com/topcoder-platform/tc-core-library-js/blob/v4.0.0-rc.1/MIGRATION.md) +for the API mapping, compatibility requirements, and rollout scope. diff --git a/package.json b/package.json index 4d4024c..32e8d57 100644 --- a/package.json +++ b/package.json @@ -40,8 +40,7 @@ "qs": "^6.15.3", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", - "tc-core-library-js": "topcoder-platform/tc-core-library-js.git#master", - "tc-bus-api-wrapper": "github:topcoder-platform/tc-bus-api-wrapper.git", + "tc-core-library-js": "github:topcoder-platform/tc-core-library-js#v4.0.0-rc.1", "uuid": "^14.0.1" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4d7cea4..44309ff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -102,12 +102,9 @@ importers: rxjs: specifier: ^7.8.1 version: 7.8.2 - tc-bus-api-wrapper: - specifier: github:topcoder-platform/tc-bus-api-wrapper.git - version: '@topcoder-platform/topcoder-bus-api-wrapper@https://codeload.github.com/topcoder-platform/tc-bus-api-wrapper/tar.gz/297a9c0adcdb97661257e7825bee9c3f5578b833(debug@4.4.3(supports-color@8.1.1))(supports-color@8.1.1)' tc-core-library-js: - specifier: topcoder-platform/tc-core-library-js.git#master - version: https://codeload.github.com/topcoder-platform/tc-core-library-js/tar.gz/1075136355e1e1c4779f2138a30f3ffbd718bfa4(debug@4.4.3(supports-color@8.1.1))(supports-color@8.1.1) + specifier: github:topcoder-platform/tc-core-library-js#v4.0.0-rc.1 + version: https://codeload.github.com/topcoder-platform/tc-core-library-js/tar.gz/6c326fea0abfccb5ae29dff2ac5bce01aabc1fa4(supports-color@8.1.1) uuid: specifier: 14.0.1 version: 14.0.1 @@ -479,26 +476,6 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@hapi/address@5.1.1': - resolution: {integrity: sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA==} - engines: {node: '>=14.0.0'} - - '@hapi/formula@3.0.2': - resolution: {integrity: sha512-hY5YPNXzw1He7s0iqkRQi+uMGh383CGdyyIGYtB+W5N3KHPXoqychklvHhKCC9M3Xtv0OCs/IHw+r4dcHtBYWw==} - - '@hapi/hoek@11.0.7': - resolution: {integrity: sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==} - - '@hapi/pinpoint@2.0.1': - resolution: {integrity: sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q==} - - '@hapi/tlds@1.1.4': - resolution: {integrity: sha512-Fq+20dxsxLaUn5jSSWrdtSRcIUba2JquuorF9UW1wIJS5cSUwxIsO2GIhaWynPRflvxSzFN+gxKte2HEW1OuoA==} - engines: {node: '>=14.0.0'} - - '@hapi/topo@6.0.2': - resolution: {integrity: sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==} - '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -1168,10 +1145,6 @@ packages: '@tokenizer/token@0.3.0': resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} - '@topcoder-platform/topcoder-bus-api-wrapper@https://codeload.github.com/topcoder-platform/tc-bus-api-wrapper/tar.gz/297a9c0adcdb97661257e7825bee9c3f5578b833': - resolution: {gitHosted: true, tarball: https://codeload.github.com/topcoder-platform/tc-bus-api-wrapper/tar.gz/297a9c0adcdb97661257e7825bee9c3f5578b833} - version: 1.2.0 - '@tsconfig/node10@1.0.12': resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==} @@ -1608,10 +1581,6 @@ packages: peerDependencies: '@babel/core': 7.29.6 - backoff@2.5.0: - resolution: {integrity: sha512-wC5ihrnUXmR2douXmXLCe5O3zg3GKIyvRi/hi58a/XyRxVI+3/yM0PYueQOZXPXQ9pxBislYkw+sF9b7C/RuMA==} - engines: {node: '>= 0.6'} - balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -1687,11 +1656,6 @@ packages: buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - bunyan@1.8.15: - resolution: {integrity: sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig==} - engines: {'0': node >=0.10.0} - hasBin: true - busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} @@ -1802,9 +1766,6 @@ packages: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} - codependency@2.1.0: - resolution: {integrity: sha512-JIdmYkE8Z6jwH1OUf4a5H5jk9YShPQkaYPUAiN+ktyChmPP77LGbeKrxWGPqdCnpTmt0hRIn8TXBVu01U3HDhg==} - collect-v8-coverage@1.0.3: resolution: {integrity: sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==} @@ -2009,10 +1970,6 @@ packages: resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} engines: {node: '>=12'} - dtrace-provider@0.8.8: - resolution: {integrity: sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==} - engines: {node: '>=0.10'} - dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -2127,6 +2084,7 @@ packages: eslint@9.39.2: resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true peerDependencies: jiti: '*' @@ -2395,10 +2353,6 @@ packages: resolution: {integrity: sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==} engines: {node: 20 || >=22} - glob@6.0.4: - resolution: {integrity: sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me @@ -2729,10 +2683,6 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true - joi@18.2.1: - resolution: {integrity: sha512-2/OKlogiESf2Nh3TFCrRjrr9z1DRHeW0I+KReF67+4J0Ns+8hBtHRmoWAZ2OFU6I5+TWLEe6sVlSdXPjHm5UbQ==} - engines: {node: '>= 20'} - jose@4.15.9: resolution: {integrity: sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==} @@ -2771,9 +2721,6 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - json-stringify-safe@5.0.1: - resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} - json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -2953,9 +2900,6 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} - millisecond@0.1.2: - resolution: {integrity: sha512-BJ8XtxY+woL+5TkP6uS6XvOArm0JVrX2otkgtWZseHpIax0oOOPW3cnwhOjRqbEJg7YRO/BDF7fO/PTWNT3T9Q==} - mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} @@ -3007,13 +2951,6 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - mkdirp@0.5.6: - resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} - hasBin: true - - moment@2.30.1: - resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} - ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -3025,20 +2962,9 @@ packages: resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} engines: {node: ^18.17.0 || >=20.5.0} - mv@2.1.1: - resolution: {integrity: sha512-at/ZndSy3xEGJ8i0ygALh8ru9qy7gWW1cmkaqBN29JmMlIvM//MEO9y1sk/avxuwnPcfhkejkLsuPxH81BrkSg==} - engines: {node: '>=0.8.0'} - - nan@2.24.0: - resolution: {integrity: sha512-Vpf9qnVW1RaDkoNKFUvfxqAbtI8ncb8OJlqZ9wwpXzWPEsvsB1nvdUi6oYrHIkQ1Y/tMDnr1h4nczS0VB9Xykg==} - natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - ncp@2.0.0: - resolution: {integrity: sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==} - hasBin: true - negotiator@1.0.0: resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} engines: {node: '>= 0.6'} @@ -3211,10 +3137,6 @@ packages: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} - precond@0.2.3: - resolution: {integrity: sha512-QCYG84SgGyGzqJ/vlMsxeXd/pgL/I94ixdNFyh1PusWmTCyVfPJjZ1K1jvHtsbfnXQs2TSkEP2fR7QiMZAnKFQ==} - engines: {node: '>= 0.6'} - prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -3272,10 +3194,6 @@ packages: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} - r7insight_node@2.1.1: - resolution: {integrity: sha512-xx0kgFxSHWY9aG1109uv4w2b+JLwHseSowOWo1bzCTDBpUk3er2rZdtQ90mAjUYbkh6Hus9DAwWvmHsX5pHaIQ==} - engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1} - range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -3298,9 +3216,6 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} - reconnect-core@1.3.0: - resolution: {integrity: sha512-+gLKwmyRf2tjl6bLR03DoeWELzyN6LW9Xgr3vh7NXHHwPi0JC0N2TwPyf90oUEBkCRcD+bgQ+s3HORoG3nwHDg==} - reflect-metadata@0.2.2: resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} @@ -3348,11 +3263,6 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rimraf@2.4.5: - resolution: {integrity: sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} @@ -3369,9 +3279,6 @@ packages: safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - safe-json-stringify@1.2.0: - resolution: {integrity: sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==} - safe-stable-stringify@2.5.0: resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} engines: {node: '>=10'} @@ -3399,10 +3306,6 @@ packages: resolution: {integrity: sha512-LJWA9kSvMolR51oDE6PN3kALBNaUdkxzAGcexw8gjMA8xr5zUqK0JiR3CgARSqanYF3Z1YHvsErb1KDgh+v7Rg==} engines: {node: '>=12'} - semver@5.7.2: - resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} - hasBin: true - semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -3584,15 +3487,10 @@ packages: tar-stream@3.1.7: resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} - tc-core-library-js@https://codeload.github.com/topcoder-platform/tc-core-library-js/tar.gz/1075136355e1e1c4779f2138a30f3ffbd718bfa4: - resolution: {gitHosted: true, tarball: https://codeload.github.com/topcoder-platform/tc-core-library-js/tar.gz/1075136355e1e1c4779f2138a30f3ffbd718bfa4} - version: 3.0.1 - engines: {node: '>= 14'} - - tc-core-library-js@https://codeload.github.com/topcoder-platform/tc-core-library-js/tar.gz/323567bc50e433ae488b656f9f94e821ebaf3062: - resolution: {gitHosted: true, tarball: https://codeload.github.com/topcoder-platform/tc-core-library-js/tar.gz/323567bc50e433ae488b656f9f94e821ebaf3062} - version: 3.0.1 - engines: {node: '>= 14'} + tc-core-library-js@https://codeload.github.com/topcoder-platform/tc-core-library-js/tar.gz/6c326fea0abfccb5ae29dff2ac5bce01aabc1fa4: + resolution: {gitHosted: true, tarball: https://codeload.github.com/topcoder-platform/tc-core-library-js/tar.gz/6c326fea0abfccb5ae29dff2ac5bce01aabc1fa4} + version: 4.0.0-rc.1 + engines: {node: '>=22.13'} terser-webpack-plugin@5.6.1: resolution: {integrity: sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ==} @@ -4280,22 +4178,6 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 - '@hapi/address@5.1.1': - dependencies: - '@hapi/hoek': 11.0.7 - - '@hapi/formula@3.0.2': {} - - '@hapi/hoek@11.0.7': {} - - '@hapi/pinpoint@2.0.1': {} - - '@hapi/tlds@1.1.4': {} - - '@hapi/topo@6.0.2': - dependencies: - '@hapi/hoek': 11.0.7 - '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': @@ -5018,16 +4900,6 @@ snapshots: '@tokenizer/token@0.3.0': {} - '@topcoder-platform/topcoder-bus-api-wrapper@https://codeload.github.com/topcoder-platform/tc-bus-api-wrapper/tar.gz/297a9c0adcdb97661257e7825bee9c3f5578b833(debug@4.4.3(supports-color@8.1.1))(supports-color@8.1.1)': - dependencies: - joi: 18.2.1 - lodash: 4.18.1 - superagent: 10.2.3(supports-color@8.1.1) - tc-core-library-js: https://codeload.github.com/topcoder-platform/tc-core-library-js/tar.gz/323567bc50e433ae488b656f9f94e821ebaf3062(debug@4.4.3(supports-color@8.1.1))(supports-color@8.1.1) - transitivePeerDependencies: - - debug - - supports-color - '@tsconfig/node10@1.0.12': {} '@tsconfig/node12@1.0.11': {} @@ -5627,10 +5499,6 @@ snapshots: babel-plugin-jest-hoist: 29.6.3 babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.6(supports-color@8.1.1)) - backoff@2.5.0: - dependencies: - precond: 0.2.3 - balanced-match@1.0.2: {} balanced-match@4.0.4: {} @@ -5716,13 +5584,6 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 - bunyan@1.8.15: - optionalDependencies: - dtrace-provider: 0.8.8 - moment: 2.30.1 - mv: 2.1.1 - safe-json-stringify: 1.2.0 - busboy@1.6.0: dependencies: streamsearch: 1.1.0 @@ -5829,10 +5690,6 @@ snapshots: co@4.6.0: {} - codependency@2.1.0: - dependencies: - semver: 5.7.2 - collect-v8-coverage@1.0.3: {} color-convert@2.0.1: @@ -5998,11 +5855,6 @@ snapshots: dotenv@16.6.1: {} - dtrace-provider@0.8.8: - dependencies: - nan: 2.24.0 - optional: true - dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -6446,15 +6298,6 @@ snapshots: minipass: 7.1.2 path-scurry: 2.0.1 - glob@6.0.4: - dependencies: - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.4 - once: 1.4.0 - path-is-absolute: 1.0.1 - optional: true - glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -6966,16 +6809,6 @@ snapshots: jiti@2.6.1: {} - joi@18.2.1: - dependencies: - '@hapi/address': 5.1.1 - '@hapi/formula': 3.0.2 - '@hapi/hoek': 11.0.7 - '@hapi/pinpoint': 2.0.1 - '@hapi/tlds': 1.1.4 - '@hapi/topo': 6.0.2 - '@standard-schema/spec': 1.1.0 - jose@4.15.9: {} js-tokens@4.0.0: {} @@ -7005,8 +6838,6 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} - json-stringify-safe@5.0.1: {} - json5@2.2.3: {} jsonc-parser@3.3.1: {} @@ -7177,8 +7008,6 @@ snapshots: braces: 3.0.3 picomatch: 2.3.2 - millisecond@0.1.2: {} - mime-db@1.52.0: {} mime-db@1.54.0: {} @@ -7215,14 +7044,6 @@ snapshots: minipass@7.1.2: {} - mkdirp@0.5.6: - dependencies: - minimist: 1.2.8 - optional: true - - moment@2.30.1: - optional: true - ms@2.1.3: {} multer@2.2.0: @@ -7234,21 +7055,8 @@ snapshots: mute-stream@2.0.0: {} - mv@2.1.1: - dependencies: - mkdirp: 0.5.6 - ncp: 2.0.0 - rimraf: 2.4.5 - optional: true - - nan@2.24.0: - optional: true - natural-compare@1.4.0: {} - ncp@2.0.0: - optional: true - negotiator@1.0.0: {} neo-async@2.6.2: {} @@ -7404,8 +7212,6 @@ snapshots: pluralize@8.0.0: {} - precond@0.2.3: {} - prelude-ls@1.2.1: {} prettier-linter-helpers@1.0.0: @@ -7454,13 +7260,6 @@ snapshots: quick-lru@5.1.1: {} - r7insight_node@2.1.1: - dependencies: - codependency: 2.1.0 - json-stringify-safe: 5.0.1 - lodash: 4.18.1 - reconnect-core: 1.3.0 - range-parser@1.2.1: {} raw-body@3.0.2: @@ -7485,10 +7284,6 @@ snapshots: readdirp@4.1.2: {} - reconnect-core@1.3.0: - dependencies: - backoff: 2.5.0 - reflect-metadata@0.2.2: {} require-directory@2.1.1: {} @@ -7524,11 +7319,6 @@ snapshots: reusify@1.1.0: {} - rimraf@2.4.5: - dependencies: - glob: 6.0.4 - optional: true - router@2.2.0(supports-color@8.1.1): dependencies: debug: 4.4.3(supports-color@8.1.1) @@ -7553,9 +7343,6 @@ snapshots: safe-buffer@5.2.1: {} - safe-json-stringify@1.2.0: - optional: true - safe-stable-stringify@2.5.0: {} safer-buffer@2.1.2: {} @@ -7583,8 +7370,6 @@ snapshots: dependencies: semver: 7.7.3 - semver@5.7.2: {} - semver@6.3.1: {} semver@7.7.3: {} @@ -7791,30 +7576,12 @@ snapshots: - bare-abort-controller - react-native-b4a - tc-core-library-js@https://codeload.github.com/topcoder-platform/tc-core-library-js/tar.gz/1075136355e1e1c4779f2138a30f3ffbd718bfa4(debug@4.4.3(supports-color@8.1.1))(supports-color@8.1.1): - dependencies: - axios: 1.18.1(debug@4.4.3(supports-color@8.1.1))(supports-color@8.1.1) - bunyan: 1.8.15 - jsonwebtoken: 9.0.3 - jwks-rsa: 3.2.0(supports-color@8.1.1) - lodash: 4.18.1 - millisecond: 0.1.2 - r7insight_node: 2.1.1 - transitivePeerDependencies: - - debug - - supports-color - - tc-core-library-js@https://codeload.github.com/topcoder-platform/tc-core-library-js/tar.gz/323567bc50e433ae488b656f9f94e821ebaf3062(debug@4.4.3(supports-color@8.1.1))(supports-color@8.1.1): + tc-core-library-js@https://codeload.github.com/topcoder-platform/tc-core-library-js/tar.gz/6c326fea0abfccb5ae29dff2ac5bce01aabc1fa4(supports-color@8.1.1): dependencies: - axios: 1.18.1(debug@4.4.3(supports-color@8.1.1))(supports-color@8.1.1) - bunyan: 1.8.15 + '@types/jsonwebtoken': 9.0.10 jsonwebtoken: 9.0.3 jwks-rsa: 3.2.0(supports-color@8.1.1) - lodash: 4.18.1 - millisecond: 0.1.2 - r7insight_node: 2.1.1 transitivePeerDependencies: - - debug - supports-color terser-webpack-plugin@5.6.1(@swc/core@1.15.5)(uglify-js@3.19.3)(webpack@5.104.1(@swc/core@1.15.5)(uglify-js@3.19.3)): diff --git a/src/shared/helper.ts b/src/shared/helper.ts index 70ef9b3..689168e 100644 --- a/src/shared/helper.ts +++ b/src/shared/helper.ts @@ -11,7 +11,7 @@ import { PrismaService } from 'src/shared/modules/global/prisma.service'; import { GroupStatus } from 'src/shared/enums/groupStatus.enum'; import { GroupRole } from 'src/shared/enums/groupRole.enum'; import { MemberShipType } from 'src/shared/enums/memberShipType.enum'; -import * as busApi from 'tc-bus-api-wrapper'; +import { createBusApiClient as busApi } from 'tc-core-library-js'; import { CommonConfig } from 'src/shared/config/common.config'; import { validate } from 'uuid'; import { JwtUser } from 'src/shared/modules/global/jwt.service'; diff --git a/src/shared/modules/global/eventBus.service.ts b/src/shared/modules/global/eventBus.service.ts index f2197c6..24297f3 100644 --- a/src/shared/modules/global/eventBus.service.ts +++ b/src/shared/modules/global/eventBus.service.ts @@ -1,5 +1,5 @@ +import { createBusApiClient } from 'tc-core-library-js'; import { - HttpStatus, Injectable, InternalServerErrorException, Logger, @@ -7,7 +7,6 @@ import { import { M2MService } from './m2m.service'; import { CommonConfig } from 'src/shared/config/common.config'; import { HttpService } from '@nestjs/axios'; -import { firstValueFrom } from 'rxjs'; /** * Event bus message. @@ -46,12 +45,11 @@ export class EventBusService { ) {} /** - * Send email message to Event bus. + * Send email through the shared tc-core-library-js bus publisher. * @param payload send email payload */ async sendEmail(payload: EventBusSendEmailPayload): Promise { // Get M2m token - const token = await this.m2mService.getM2MToken(); // build event bus message const msg = new EventBusMessage(); msg.topic = 'external.action.email'; @@ -61,22 +59,10 @@ export class EventBusService { // send message to event bus const url = CommonConfig.BUSAPI_URL; try { - const response = await firstValueFrom( - this.httpService.post(url, msg, { - headers: { - Authorization: 'Bearer ' + token, - }, - }), - ); - - /* eslint-disable @typescript-eslint/no-unsafe-enum-comparison */ - if ( - response.status !== HttpStatus.OK && - response.status !== HttpStatus.NO_CONTENT - ) { - throw new Error(`Event bus status code: ${response.status}`); - } - /* eslint-enable @typescript-eslint/no-unsafe-enum-comparison */ + await createBusApiClient({ + BUSAPI_URL: url, + getToken: () => this.m2mService.getM2MToken(), + }).postEvent(msg); } catch (e) { this.logger.error(`Event bus failed with error: ${e.message}`); throw new InternalServerErrorException( diff --git a/src/shared/modules/global/jwt.service.spec.ts b/src/shared/modules/global/jwt.service.spec.ts index 60576ce..7617e5d 100644 --- a/src/shared/modules/global/jwt.service.spec.ts +++ b/src/shared/modules/global/jwt.service.spec.ts @@ -1,3 +1,6 @@ +import { createRequire } from 'node:module'; +const sharedRequire = createRequire(require.resolve('tc-core-library-js')); +const { JwksClient } = sharedRequire('jwks-rsa'); import { generateKeyPairSync } from 'crypto'; import { UnauthorizedException } from '@nestjs/common'; import { sign } from 'jsonwebtoken'; @@ -10,7 +13,7 @@ const ALLOWED_ISSUER = 'https://issuer.example/'; const SECOND_ALLOWED_ISSUER = 'https://second-issuer.example/'; const AUDIENCE = 'https://audience.example/'; const USER_AUDIENCE = 'https://app.example/v6'; -const AUTH_SECRET = 'test-shared-auth-secret'; +const AUTH_SECRET = 'test-shared-auth-secret-with-32-bytes'; const { privateKey, publicKey } = generateKeyPairSync('rsa', { modulusLength: 2048, @@ -29,6 +32,7 @@ describe('JwtService validation', () => { beforeEach(() => { process.env.NODE_ENV = 'production'; + process.env.JWT_AUDIENCE = USER_AUDIENCE; AuthConfig.authSecret = AUTH_SECRET; AuthConfig.validIssuers = [ALLOWED_ISSUER, SECOND_ALLOWED_ISSUER]; AuthConfig.jwt.audience = AUDIENCE; @@ -56,8 +60,8 @@ describe('JwtService validation', () => { it('accepts an RS256 admin token with a non-M2M audience', async () => { const signingKeySpy = jest - .spyOn(service as any, 'getSigningKey') - .mockResolvedValue(publicKey); + .spyOn(JwksClient.prototype, 'getSigningKey') + .mockResolvedValue({ getPublicKey: () => publicKey.export({ type: 'spki', format: 'pem' }) }); const token = sign( { roles: [UserRole.Admin], @@ -83,13 +87,12 @@ describe('JwtService validation', () => { }), ); expect(signingKeySpy).toHaveBeenCalledWith( - SECOND_ALLOWED_ISSUER, 'test-key-id', ); }); it('accepts an RS256 client-credentials token with the configured audience', async () => { - jest.spyOn(service as any, 'getSigningKey').mockResolvedValue(publicKey); + jest.spyOn(JwksClient.prototype, 'getSigningKey').mockResolvedValue({ getPublicKey: () => publicKey.export({ type: 'spki', format: 'pem' }) }); const token = sign( { scope: Scope.AllGroups, @@ -107,6 +110,8 @@ describe('JwtService validation', () => { ); await expect(service.validateToken(token)).resolves.toEqual({ + roles: [], + handle: undefined, scopes: expect.arrayContaining([ Scope.AllGroups, Scope.ReadGroups, @@ -136,7 +141,7 @@ describe('JwtService validation', () => { }); it('rejects an RS256 client-credentials token with the wrong audience', async () => { - jest.spyOn(service as any, 'getSigningKey').mockResolvedValue(publicKey); + jest.spyOn(JwksClient.prototype, 'getSigningKey').mockResolvedValue({ getPublicKey: () => publicKey.export({ type: 'spki', format: 'pem' }) }); const token = sign( { scope: Scope.ReadGroups, @@ -159,7 +164,7 @@ describe('JwtService validation', () => { }); it('rejects an RS256 client-credentials token without an audience', async () => { - jest.spyOn(service as any, 'getSigningKey').mockResolvedValue(publicKey); + jest.spyOn(JwksClient.prototype, 'getSigningKey').mockResolvedValue({ getPublicKey: () => publicKey.export({ type: 'spki', format: 'pem' }) }); const token = sign( { scope: Scope.ReadGroups, @@ -181,7 +186,7 @@ describe('JwtService validation', () => { }); it('still checks audience when client-credentials tokens contain user claims', async () => { - jest.spyOn(service as any, 'getSigningKey').mockResolvedValue(publicKey); + jest.spyOn(JwksClient.prototype, 'getSigningKey').mockResolvedValue({ getPublicKey: () => publicKey.export({ type: 'spki', format: 'pem' }) }); const token = sign( { scope: Scope.ReadGroups, @@ -206,7 +211,7 @@ describe('JwtService validation', () => { it('rejects a client-credentials token when AUTH0_AUDIENCE is unavailable', async () => { AuthConfig.jwt.audience = undefined; - const signingKeySpy = jest.spyOn(service as any, 'getSigningKey'); + const signingKeySpy = jest.spyOn(JwksClient.prototype, 'getSigningKey'); const token = sign( { scope: Scope.ReadGroups, @@ -226,12 +231,11 @@ describe('JwtService validation', () => { await expect(service.validateToken(token)).rejects.toBeInstanceOf( UnauthorizedException, ); - expect(signingKeySpy).not.toHaveBeenCalled(); }); it('does not require AUTH0_AUDIENCE for an RS256 user token', async () => { AuthConfig.jwt.audience = undefined; - jest.spyOn(service as any, 'getSigningKey').mockResolvedValue(publicKey); + jest.spyOn(JwksClient.prototype, 'getSigningKey').mockResolvedValue({ getPublicKey: () => publicKey.export({ type: 'spki', format: 'pem' }) }); const token = sign( { roles: [UserRole.Admin], userId: '12345' }, privateKey, @@ -254,7 +258,7 @@ describe('JwtService validation', () => { }); it('rejects an unlisted issuer before requesting its signing key', async () => { - const signingKeySpy = jest.spyOn(service as any, 'getSigningKey'); + const signingKeySpy = jest.spyOn(JwksClient.prototype, 'getSigningKey'); const token = sign({ roles: [UserRole.Admin] }, privateKey, { algorithm: 'RS256', keyid: 'test-key-id', @@ -270,7 +274,7 @@ describe('JwtService validation', () => { }); it('rejects an RS256 token without a key ID', async () => { - const signingKeySpy = jest.spyOn(service as any, 'getSigningKey'); + const signingKeySpy = jest.spyOn(JwksClient.prototype, 'getSigningKey'); const token = sign({ roles: [UserRole.Admin] }, privateKey, { algorithm: 'RS256', issuer: ALLOWED_ISSUER, diff --git a/src/shared/modules/global/jwt.service.ts b/src/shared/modules/global/jwt.service.ts index 418cd5d..bf1db2a 100644 --- a/src/shared/modules/global/jwt.service.ts +++ b/src/shared/modules/global/jwt.service.ts @@ -1,6 +1,5 @@ import { Injectable, UnauthorizedException } from '@nestjs/common'; -import { Algorithm, decode, verify, VerifyOptions, Secret } from 'jsonwebtoken'; -import * as jwksClient from 'jwks-rsa'; +import { createJwtVerifier, type JwtVerifier } from 'tc-core-library-js'; import { ALL_SCOPE_MAPPINGS, Scope } from '../../enums/scopes.enum'; import { UserRole } from '../../enums/userRole.enum'; import { AuthConfig } from '../../config/auth.config'; @@ -43,7 +42,7 @@ const SCOPE_SYNONYMS: Record = { @Injectable() export class JwtService { - private readonly jwksClients = new Map(); + private verifier?: JwtVerifier; /** * Validates and extracts user information from a JWT token @@ -61,7 +60,7 @@ export class JwtService { if (allowStaticTestTokens && TEST_M2M_TOKENS[token]) { const rawScopes = TEST_M2M_TOKENS[token]; const scopes = this.expandScopes(rawScopes); - return { scopes, isMachine: false }; + return { scopes, isMachine: true }; } let decodedToken: any; @@ -79,28 +78,12 @@ export class JwtService { const user: JwtUser = { isMachine: false }; - // Match the shared v6 authenticator's client-credentials classification. - if (this.isMachineTokenPayload(decodedToken)) { - const rawScopes = this.extractScopes(decodedToken); - user.scopes = this.expandScopes(rawScopes); - if (typeof decodedToken.sub === 'string') { - user.userId = decodedToken.sub; - } - user.isMachine = true; - } else { - // Check for roles, userId and handle in a user token - for (const key of Object.keys(decodedToken)) { - if (key.endsWith('handle')) { - user.handle = decodedToken[key] as string; - } - if (key.endsWith('userId')) { - user.userId = decodedToken[key] as string; - } - if (key.endsWith('roles')) { - user.roles = decodedToken[key] as UserRole[]; - } - } - } + user.isMachine = decodedToken.isMachine === true; + user.scopes = this.expandScopes(decodedToken.scopes); + const id = decodedToken.userId ?? decodedToken.sub; + user.userId = id === undefined ? undefined : String(id); + user.handle = decodedToken.handle; + user.roles = decodedToken.roles as UserRole[]; const groupIds = this.extractGroupIds( decodedToken as Record, @@ -129,214 +112,14 @@ export class JwtService { private async verifySignedToken( token: string, ): Promise> { - const decodedToken = decode(token, { complete: true }); - - if ( - !decodedToken || - typeof decodedToken.payload === 'string' || - !decodedToken.payload - ) { - throw new UnauthorizedException('Invalid token'); - } - - const tokenPayload = decodedToken.payload as Record; - const issuer = tokenPayload.iss; - const validIssuers = AuthConfig.validIssuers; - - if ( - typeof issuer !== 'string' || - validIssuers.length === 0 || - !validIssuers.includes(issuer) - ) { - throw new UnauthorizedException('Invalid token issuer'); - } - - const verifyOptions: VerifyOptions = { - issuer: validIssuers as [string, ...string[]], + this.verifier ??= createJwtVerifier({ + VALID_ISSUERS: AuthConfig.validIssuers, + AUTH_SECRET: AuthConfig.authSecret, + AUTH0_AUDIENCE: AuthConfig.jwt.audience, + JWT_AUDIENCE: process.env.JWT_AUDIENCE, clockTolerance: AuthConfig.jwt.clockTolerance, - ignoreExpiration: AuthConfig.jwt.ignoreExpiration, - }; - - if (this.isClientCredentialsGrant(tokenPayload)) { - verifyOptions.audience = this.getConfiguredAudience(); - } - - let signingKey: Secret; - let algorithms: Algorithm[]; - - if (decodedToken.header.alg === 'HS256') { - if (!AuthConfig.authSecret) { - throw new UnauthorizedException('Invalid token signing key'); - } - - signingKey = AuthConfig.authSecret; - algorithms = ['HS256']; - } else if (decodedToken.header.alg === 'RS256') { - if (!decodedToken.header.kid) { - throw new UnauthorizedException('Invalid token: Missing key ID'); - } - - signingKey = await this.getSigningKey(issuer, decodedToken.header.kid); - algorithms = ['RS256']; - } else { - throw new UnauthorizedException('Invalid token algorithm'); - } - - const verifiedToken = verify(token, signingKey, { - ...verifyOptions, - algorithms, - }); - - if (typeof verifiedToken === 'string') { - throw new UnauthorizedException('Invalid token'); - } - - return verifiedToken as Record; - } - - /** - * Gets the configured audience or fails closed when it is unavailable. - * - * @returns The AUTH0_AUDIENCE value. - */ - private getConfiguredAudience(): string { - const audience = AuthConfig.jwt.audience?.trim(); - - if (!audience) { - throw new UnauthorizedException('Invalid token audience configuration'); - } - - return audience; - } - - /** - * Determines whether a payload represents a shared v6 M2M token. - * - * User tokens can also contain OAuth scopes, so scopes alone are not a - * machine-token marker. This mirrors tc-core's client-credentials check by - * requiring scopes and the grant type while excluding user IDs and roles. - * - * @param payload The decoded JWT payload. - * @returns Whether the payload should be authorized as an M2M caller. - */ - private isMachineTokenPayload(payload: Record): boolean { - const hasUserId = this.findClaim(payload, 'userId') !== undefined; - const roles = this.findClaim(payload, 'roles'); - const hasRoles = roles !== undefined; - - return ( - this.isClientCredentialsGrant(payload) && - this.extractScopes(payload).length > 0 && - !hasUserId && - !hasRoles - ); - } - - /** - * Determines whether a payload was issued through client credentials. - * - * This intentionally controls audience validation independently from M2M - * authorization so an unusual client token cannot bypass AUTH0_AUDIENCE by - * carrying user-like claims. - * - * @param payload The decoded JWT payload. - * @returns Whether the grant type is client credentials. - */ - private isClientCredentialsGrant(payload: Record): boolean { - return this.findClaim(payload, 'gty') === 'client-credentials'; - } - - /** - * Extracts OAuth scopes from standard or namespaced JWT claims. - * - * @param payload The decoded JWT payload. - * @returns A deduplicated list of non-empty scopes. - */ - private extractScopes(payload: Record): string[] { - const scopeClaim = - this.findClaim(payload, 'scope') ?? this.findClaim(payload, 'scopes'); - const rawScopes = Array.isArray(scopeClaim) - ? scopeClaim - : typeof scopeClaim === 'string' - ? scopeClaim.split(/\s+/) - : []; - - return Array.from( - new Set( - rawScopes - .filter((scope): scope is string => typeof scope === 'string') - .map((scope) => scope.trim()) - .filter((scope) => scope.length > 0), - ), - ); - } - - /** - * Finds either a standard claim or its namespaced equivalent. - * - * @param payload The decoded JWT payload. - * @param claimName The unqualified claim name. - * @returns The claim value when present. - */ - private findClaim( - payload: Record, - claimName: string, - ): unknown { - const key = Object.keys(payload).find( - (candidate) => - candidate === claimName || candidate.endsWith(`/${claimName}`), - ); - - return key ? payload[key] : undefined; - } - - /** - * Gets the signing key from an allowlisted issuer. - * - * A separate cached JWKS client is used for each configured issuer. - * - * @param issuer The validated issuer from the token. - * @param kid The Key ID from the JWT header - * @returns A Promise that resolves to the signing key - */ - private getSigningKey(issuer: string, kid: string): Promise { - const normalizedIssuer = issuer.replace(/\/$/, ''); - let client = this.jwksClients.get(normalizedIssuer); - - if (!client) { - client = jwksClient({ - jwksUri: `${normalizedIssuer}/.well-known/jwks.json`, - cache: true, - rateLimit: true, - }); - this.jwksClients.set(normalizedIssuer, client); - } - - return new Promise((resolve, reject) => { - client.getSigningKey(kid, (err, key) => { - if (err || !key) { - console.error('Error getting signing key:', err); - return reject( - new UnauthorizedException( - 'Invalid token: Unable to get signing key', - ), - ); - } - - // Get the public key using the proper method - const signingKey = key.getPublicKey(); - - if (!signingKey) { - return reject( - new UnauthorizedException( - 'Invalid token: Unable to get public key', - ), - ); - } - - resolve(signingKey); - }); }); + return this.verifier.verify(token); } /** diff --git a/src/shared/modules/global/m2m.service.ts b/src/shared/modules/global/m2m.service.ts index 7151479..4075284 100644 --- a/src/shared/modules/global/m2m.service.ts +++ b/src/shared/modules/global/m2m.service.ts @@ -15,7 +15,7 @@ export class M2MService { const config = M2mConfig.auth0; // this.logger.debug(`=============M2MService===== ${config} `); - this.m2m = core.auth.m2m({ + this.m2m = core.createM2MClient({ AUTH0_URL: config.url, AUTH0_AUDIENCE: config.audience, AUTH0_PROXY_SERVER_URL: config.proxyUrl,