fix(user-settings): improve route matching for keys containing slashes (#32382)

Signed-off-by: Teijo Mursu <zcmander@gmail.com>
This commit is contained in:
Teijo Mursu
2026-01-20 15:57:48 +02:00
committed by GitHub
parent 4452d15efa
commit ad01e54037
3 changed files with 75 additions and 4 deletions
@@ -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')
@@ -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;