diff --git a/package-lock.json b/package-lock.json index 6e54cabd7..ce7137e55 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "promise-pool-executor": "^1.1.1", "sanitize-filename": "^1.6.3", "undici": "^7.16.0", - "winston": "^3.18.3", + "winston": "^3.19.0", "yargs": "^15.4.1" }, "bin": { @@ -28,7 +28,7 @@ }, "devDependencies": { "@types/fs-extra": "^9.0.13", - "@types/lodash": "^4.17.20", + "@types/lodash": "^4.17.21", "@types/mocha": "^10.0.10", "@types/nconf": "^0.10.7", "@typescript-eslint/parser": "^5.62.0", @@ -51,7 +51,7 @@ "sinon": "^13.0.2", "sinon-chai": "^3.7.0", "ts-mocha": "^10.1.0", - "typescript": "^5.9.2", + "typescript": "^5.9.3", "zlib": "^1.0.5" }, "engines": { @@ -956,9 +956,9 @@ "dev": true }, "node_modules/@types/lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==", "dev": true, "license": "MIT" }, @@ -6501,9 +6501,9 @@ } }, "node_modules/typescript": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", - "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -6749,9 +6749,9 @@ } }, "node_modules/winston": { - "version": "3.18.3", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.18.3.tgz", - "integrity": "sha512-NoBZauFNNWENgsnC9YpgyYwOVrl2m58PpQ8lNHjV3kosGs7KJ7Npk9pCUE+WJlawVSe8mykWDKWFSVfs3QO9ww==", + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", + "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", "license": "MIT", "dependencies": { "@colors/colors": "^1.6.0", @@ -7729,9 +7729,9 @@ "dev": true }, "@types/lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==", "dev": true }, "@types/mocha": { @@ -11648,9 +11648,9 @@ } }, "typescript": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", - "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true }, "unbox-primitive": { @@ -11820,9 +11820,9 @@ } }, "winston": { - "version": "3.18.3", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.18.3.tgz", - "integrity": "sha512-NoBZauFNNWENgsnC9YpgyYwOVrl2m58PpQ8lNHjV3kosGs7KJ7Npk9pCUE+WJlawVSe8mykWDKWFSVfs3QO9ww==", + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", + "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", "requires": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.8", diff --git a/package.json b/package.json index 90314c1f0..f0c40fe3d 100644 --- a/package.json +++ b/package.json @@ -43,12 +43,12 @@ "promise-pool-executor": "^1.1.1", "sanitize-filename": "^1.6.3", "undici": "^7.16.0", - "winston": "^3.18.3", + "winston": "^3.19.0", "yargs": "^15.4.1" }, "devDependencies": { "@types/fs-extra": "^9.0.13", - "@types/lodash": "^4.17.20", + "@types/lodash": "^4.17.21", "@types/mocha": "^10.0.10", "@types/nconf": "^0.10.7", "@typescript-eslint/parser": "^5.62.0", @@ -71,7 +71,7 @@ "sinon": "^13.0.2", "sinon-chai": "^3.7.0", "ts-mocha": "^10.1.0", - "typescript": "^5.9.2", + "typescript": "^5.9.3", "zlib": "^1.0.5" }, "engines": { diff --git a/src/tools/auth0/handlers/resourceServers.ts b/src/tools/auth0/handlers/resourceServers.ts index 99786f98e..8eb3a1e49 100644 --- a/src/tools/auth0/handlers/resourceServers.ts +++ b/src/tools/auth0/handlers/resourceServers.ts @@ -1,4 +1,5 @@ import { + ApiResponse, ResourceServer, ResourceServerProofOfPossessionMechanismEnum, ResourceServerSubjectTypeAuthorizationClientPolicyEnum, @@ -92,8 +93,11 @@ export default class ResourceServersHandler extends DefaultHandler { ...options, type: 'resourceServers', identifiers: ['id', 'identifier'], - stripCreateFields: ['client_id'], + stripCreateFields: ['client_id', 'is_system'], stripUpdateFields: ['identifier', 'client_id', 'is_system'], + functions: { + update: (args, data) => this.updateResourceServer(args, data), + }, }); } @@ -104,13 +108,44 @@ export default class ResourceServersHandler extends DefaultHandler { async getType(): Promise { if (this.existing) return this.existing; - const resourceServers = await paginate(this.client.resourceServers.getAll, { + let resourceServers = await paginate(this.client.resourceServers.getAll, { paginate: true, include_totals: true, }); - return resourceServers.filter( + + resourceServers = resourceServers.filter( (rs) => rs.name !== constants.RESOURCE_SERVERS_MANAGEMENT_API_NAME ); + + // Sanitize resource servers fields + const sanitizeResourceServersFields = (rs: ResourceServer[]): ResourceServer[] => + rs.map((resourceServer: ResourceServer) => { + // For system resource servers like Auth0 My Account API, only allow certain fields to be updated + if (resourceServer.is_system === true) { + const allowedKeys = [ + 'token_lifetime', + 'proof_of_possession', + 'skip_consent_for_verifiable_first_party_clients', + 'name', + 'identifier', + 'id', + 'is_system', + ]; + const sanitized: any = {}; + allowedKeys.forEach((key) => { + if (key in resourceServer) { + sanitized[key] = resourceServer[key]; + } + }); + return sanitized; + } + + return resourceServer; + }); + + this.existing = sanitizeResourceServersFields(resourceServers); + + return this.existing; } async calcChanges(assets: Assets): Promise { @@ -159,4 +194,14 @@ export default class ResourceServersHandler extends DefaultHandler { await super.validate(assets); } + + async updateResourceServer(args, update: ResourceServer): Promise> { + // Exclude name from update as it cannot be modified for system resource servers like Auth0 My Account API + if (update.is_system === true || update.name === 'Auth0 My Account API') { + const { name, is_system: _isSystem, ...updateFields } = update; + return this.client.resourceServers.update(args, updateFields); + } + + return this.client.resourceServers.update(args, update); + } } diff --git a/test/tools/auth0/handlers/resourceServers.tests.js b/test/tools/auth0/handlers/resourceServers.tests.js index ad9bbdf38..571e7b0b8 100644 --- a/test/tools/auth0/handlers/resourceServers.tests.js +++ b/test/tools/auth0/handlers/resourceServers.tests.js @@ -621,5 +621,83 @@ describe('#resourceServers handler', () => { expect(result[0]).to.have.property('name', 'someAPI'); expect(result[0]).to.have.property('identifier', 'some-api'); }); + + it('should sanitize system resource servers in getType for Auth0 My Account API', async () => { + const systemResourceServer = { + id: 'rs_system', + identifier: 'https://api.system.com/me/', + name: 'Auth0 My Account API', + is_system: true, + token_lifetime: 86400, + scopes: [{ value: 'read:users' }], // Should be removed + signing_alg: 'RS256', // Should be removed + allow_offline_access: true, // Should be removed + skip_consent_for_verifiable_first_party_clients: true, + enforce_policies: true, // Should be removed + token_dialect: 'access_token', // Should be removed + }; + + const auth0 = { + resourceServers: { + getAll: (params) => mockPagedData(params, 'resourceServers', [systemResourceServer]), + }, + pool, + }; + + const handler = new resourceServers.default({ client: pageClient(auth0), config }); + const result = await handler.getType(); + + expect(result).to.be.an('array'); + expect(result[0]).to.deep.equal({ + id: 'rs_system', + identifier: 'https://api.system.com/me/', + name: 'Auth0 My Account API', + is_system: true, + token_lifetime: 86400, + skip_consent_for_verifiable_first_party_clients: true, + }); + }); + + it('should update "Auth0 My Account API" without name and is_system', async () => { + let updateCalled = false; + const existingResourceServer = { + id: 'rs_my_account', + identifier: 'https://auth0.com/my-account/me/', + name: 'Auth0 My Account API', + is_system: true, + }; + + const auth0 = { + resourceServers: { + create: () => Promise.resolve({ data: [] }), + update: function (params, data) { + updateCalled = true; + expect(params.id).to.equal('rs_my_account'); + expect(data.name).to.equal(undefined); + expect(data.is_system).to.equal(undefined); + expect(data.token_lifetime).to.equal(54321); + return Promise.resolve({ data }); + }, + delete: () => Promise.resolve({ data: [] }), + getAll: (params) => mockPagedData(params, 'resourceServers', [existingResourceServer]), + }, + pool, + }; + + const handler = new resourceServers.default({ client: pageClient(auth0), config }); + const stageFn = Object.getPrototypeOf(handler).processChanges; + const data = { + resourceServers: [ + { + name: 'Auth0 My Account API', + identifier: 'https://auth0.com/my-account/me/', + token_lifetime: 54321, + }, + ], + }; + + await stageFn.apply(handler, [data]); + expect(updateCalled).to.equal(true); + }); }); });