diff --git a/lib/actions/registration.js b/lib/actions/registration.js index 93c612fc2..fff0b895f 100644 --- a/lib/actions/registration.js +++ b/lib/actions/registration.js @@ -39,7 +39,7 @@ const validateRegistrationAccessToken = [ ); ctx.assert(regAccessToken, new InvalidToken('token not found')); - const client = await ctx.oidc.provider.Client.find(ctx.params.clientId); + const client = await ctx.oidc.provider.Client.find(decodeURIComponent(ctx.params.clientId)); if (!client || client.clientId !== regAccessToken.clientId) { await regAccessToken.destroy(); @@ -139,7 +139,7 @@ module.exports = { if (rat) { Object.assign(ctx.body, { registration_client_uri: ctx.oidc.urlFor('client', { - clientId: properties.client_id, + clientId: encodeURIComponent(properties.client_id), }), registration_access_token: await rat.save(), }); diff --git a/test/client_id_uri/client_id_uri.config.js b/test/client_id_uri/client_id_uri.config.js new file mode 100644 index 000000000..037aa9aa2 --- /dev/null +++ b/test/client_id_uri/client_id_uri.config.js @@ -0,0 +1,22 @@ +const cloneDeep = require('lodash/cloneDeep'); +const merge = require('lodash/merge'); + +const nanoid = require('../../lib/helpers/nanoid'); +const config = cloneDeep(require('../default.config')); + +merge(config.features, { + registration: { + enabled: true, + rotateRegistrationAccessToken: false, + idFactory() { + return new URL(`https://repo.clients.com/path?id=${nanoid()}`).href; + }, + }, + registrationManagement: { + enabled: true, + }, +}); + +module.exports = { + config, +}; diff --git a/test/client_id_uri/client_id_uri.test.js b/test/client_id_uri/client_id_uri.test.js new file mode 100644 index 000000000..4c12fc724 --- /dev/null +++ b/test/client_id_uri/client_id_uri.test.js @@ -0,0 +1,59 @@ +const { expect } = require('chai'); + +const bootstrap = require('../test_helper'); + +describe('registration management with client_id as URI', () => { + before(bootstrap(__dirname)); + + it('returns client_id as a URI string', async function () { + let client_id; + let registration_client_uri; + let registration_access_token; + + await this.agent.post('/reg') + .send({ + redirect_uris: ['https://client.example.com/cb'], + }) + .expect(201) + .expect((response) => { + ({ client_id, registration_access_token, registration_client_uri } = response.body); + + const parsed = new URL(registration_client_uri); + expect(parsed.search).to.be.empty; + const i = parsed.pathname.indexOf('/reg/'); + expect(parsed.pathname.slice(i + 5)).to.equal(encodeURIComponent(client_id)); + }); + + await this.agent.get(new URL(registration_client_uri).pathname) + .auth(registration_access_token, { type: 'bearer' }) + .expect(200) + .expect((response) => { + ({ registration_client_uri } = response.body); + + const parsed = new URL(registration_client_uri); + expect(parsed.search).to.be.empty; + const i = parsed.pathname.indexOf('/reg/'); + expect(parsed.pathname.slice(i + 5)).to.equal(encodeURIComponent(client_id)); + }); + + await this.agent.put(new URL(registration_client_uri).pathname) + .auth(registration_access_token, { type: 'bearer' }) + .send({ + client_id, + redirect_uris: ['https://client.example.com/cb2'], + }) + .expect(200) + .expect((response) => { + ({ registration_client_uri } = response.body); + + const parsed = new URL(registration_client_uri); + expect(parsed.search).to.be.empty; + const i = parsed.pathname.indexOf('/reg/'); + expect(parsed.pathname.slice(i + 5)).to.equal(encodeURIComponent(client_id)); + }); + + await this.agent.delete(new URL(registration_client_uri).pathname) + .auth(registration_access_token, { type: 'bearer' }) + .expect(204); + }); +});