diff --git a/.changeset/quick-states-enjoy.md b/.changeset/quick-states-enjoy.md new file mode 100644 index 0000000000..b6c5303c48 --- /dev/null +++ b/.changeset/quick-states-enjoy.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-user-settings-backend': patch +--- + +Resolves an issue where user setting keys containing slashes returned 404 not found. diff --git a/plugins/user-settings-backend/src/service/router.test.ts b/plugins/user-settings-backend/src/service/router.test.ts index afc725025c..01277a9fd3 100644 --- a/plugins/user-settings-backend/src/service/router.test.ts +++ b/plugins/user-settings-backend/src/service/router.test.ts @@ -74,6 +74,30 @@ describe('createRouter', () => { }); }); + it('returns ok for keys with forward slashes', async () => { + const setting = { + bucket: 'my-bucket', + key: 'my-key/with/slashes', + value: 'a', + }; + + userSettingsStore.get.mockResolvedValue(setting); + + const responses = await request(app).get( + '/buckets/my-bucket/keys/my-key/with/slashes', + ); + + expect(responses.status).toEqual(200); + expect(responses.body).toEqual(setting); + + expect(userSettingsStore.get).toHaveBeenCalledTimes(1); + expect(userSettingsStore.get).toHaveBeenCalledWith({ + userEntityRef: mockUserRef, + bucket: 'my-bucket', + key: 'my-key/with/slashes', + }); + }); + it('returns an error if the Authorization header is missing', async () => { const responses = await request(app) .get('/buckets/my-bucket/keys/my-key') @@ -107,6 +131,28 @@ describe('createRouter', () => { }); }); + it('returns ok for keys with forward slashes', async () => { + userSettingsStore.delete.mockResolvedValue(); + + const responses = await request(app).delete( + '/buckets/my-bucket/keys/my-key/with/slashes', + ); + + expect(responses.status).toEqual(204); + + expect(userSettingsStore.delete).toHaveBeenCalledTimes(1); + expect(userSettingsStore.delete).toHaveBeenCalledWith({ + userEntityRef: mockUserRef, + bucket: 'my-bucket', + key: 'my-key/with/slashes', + }); + expect(signalService.publish).toHaveBeenCalledWith({ + recipients: { type: 'user', entityRef: mockUserRef }, + channel: `user-settings`, + message: { type: 'key-deleted', key: 'my-key/with/slashes' }, + }); + }); + it('returns an error if the Authorization header is missing', async () => { const responses = await request(app) .delete('/buckets/my-bucket/keys/my-key') @@ -151,6 +197,26 @@ describe('createRouter', () => { }); }); + it('returns ok for keys with forward slashes', async () => { + const setting = { + bucket: 'my-bucket', + key: 'my-key/with/slashes', + value: 'a', + }; + + userSettingsStore.set.mockResolvedValue(); + userSettingsStore.get.mockResolvedValue(setting); + + const responses = await request(app) + .put('/buckets/my-bucket/keys/my-key/with/slashes') + .send({ value: 'a' }); + + expect(responses.status).toEqual(200); + expect(responses.body).toEqual(setting); + + expect(userSettingsStore.set).toHaveBeenCalledTimes(1); + }); + it('returns an error if the value is not given', async () => { const responses = await request(app) .put('/buckets/my-bucket/keys/my-key') diff --git a/plugins/user-settings-backend/src/service/router.ts b/plugins/user-settings-backend/src/service/router.ts index 07b4443551..3a0de7054c 100644 --- a/plugins/user-settings-backend/src/service/router.ts +++ b/plugins/user-settings-backend/src/service/router.ts @@ -41,7 +41,7 @@ export async function createRouter(options: { }; // get a single value - router.get('/buckets/:bucket/keys/:key', async (req, res) => { + router.get('/buckets/:bucket/keys/:key(*)', async (req, res) => { const userEntityRef = await getUserEntityRef(req); const { bucket, key } = req.params; @@ -55,7 +55,7 @@ export async function createRouter(options: { }); // set a single value - router.put('/buckets/:bucket/keys/:key', async (req, res) => { + router.put('/buckets/:bucket/keys/:key(*)', async (req, res) => { const userEntityRef = await getUserEntityRef(req); const { bucket, key } = req.params; const { value } = req.body; @@ -87,8 +87,8 @@ export async function createRouter(options: { res.json(setting); }); - // get a single value - router.delete('/buckets/:bucket/keys/:key', async (req, res) => { + // delete a single value + router.delete('/buckets/:bucket/keys/:key(*)', async (req, res) => { const userEntityRef = await getUserEntityRef(req); const { bucket, key } = req.params;