refactor(server)!: sanitize error messages to avoid leaking resource and permission details

This commit is contained in:
timonrieger
2026-04-29 14:45:46 +02:00
parent 65bd0a9320
commit b96421a083
31 changed files with 129 additions and 128 deletions
+4 -4
View File
@@ -10,9 +10,9 @@ export const errorDto = {
forbidden: {
message: expect.any(String),
},
missingPermission: (permission: string) => ({
message: `Missing required permission: ${permission}`,
}),
missingPermission: {
message: 'Access denied',
},
wrongPassword: {
message: 'Wrong password',
},
@@ -29,7 +29,7 @@ export const errorDto = {
message: message ?? expect.anything(),
}),
noPermission: {
message: expect.stringContaining('Not found or no'),
message: 'Access denied',
},
incorrectLogin: {
message: 'Incorrect email or password',
@@ -323,8 +323,8 @@ describe('/activities', () => {
.delete(`/activities/${reaction.id}`)
.set('Authorization', `Bearer ${nonOwner.accessToken}`);
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('Not found or no activity.delete access'));
expect(status).toBe(403);
expect(body).toEqual(errorDto.noPermission);
});
it('should let a non-owner remove their own comment', async () => {
+9 -9
View File
@@ -480,8 +480,8 @@ describe('/albums', () => {
.set('Authorization', `Bearer ${user2.accessToken}`)
.send({ ids: [asset.id] });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('Not found or no albumAsset.create access'));
expect(status).toBe(403);
expect(body).toEqual(errorDto.noPermission);
});
it('should add duplicate assets only once', async () => {
@@ -526,8 +526,8 @@ describe('/albums', () => {
.set('Authorization', `Bearer ${user2.accessToken}`)
.send({ albumName: 'New album name' });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('Not found or no album.update access'));
expect(status).toBe(403);
expect(body).toEqual(errorDto.noPermission);
});
it('should be able to update as an editor', async () => {
@@ -553,7 +553,7 @@ describe('/albums', () => {
.set('Authorization', `Bearer ${user2.accessToken}`)
.send({ ids: [user1Asset1.id] });
expect(status).toBe(400);
expect(status).toBe(403);
expect(body).toEqual(errorDto.noPermission);
});
@@ -614,8 +614,8 @@ describe('/albums', () => {
.set('Authorization', `Bearer ${user2.accessToken}`)
.send({ ids: [user1Asset1.id] });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('Not found or no albumAsset.delete access'));
expect(status).toBe(403);
expect(body).toEqual(errorDto.noPermission);
});
it('should remove duplicate assets only once', async () => {
@@ -728,8 +728,8 @@ describe('/albums', () => {
.set('Authorization', `Bearer ${user2.accessToken}`)
.send({ role: AlbumUserRole.Editor });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('Not found or no album.share access'));
expect(status).toBe(403);
expect(body).toEqual(errorDto.noPermission);
});
});
});
+1 -1
View File
@@ -28,7 +28,7 @@ describe('/api-keys', () => {
const { secret } = await create(user.accessToken, [Permission.ApiKeyRead]);
const { status, body } = await request(app).post('/api-keys').set('x-api-key', secret).send({ name: 'API Key' });
expect(status).toBe(403);
expect(body).toEqual(errorDto.missingPermission('apiKey.create'));
expect(body).toEqual(errorDto.missingPermission);
});
it('should work with apiKey.create', async () => {
+4 -4
View File
@@ -157,7 +157,7 @@ describe('/asset', () => {
const { status, body } = await request(app)
.get(`/assets/${user2Assets[0].id}`)
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(400);
expect(status).toBe(403);
expect(body).toEqual(errorDto.noPermission);
});
@@ -385,7 +385,7 @@ describe('/asset', () => {
.put(`/assets/${user2Assets[0].id}`)
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({});
expect(status).toBe(400);
expect(status).toBe(403);
expect(body).toEqual(errorDto.noPermission);
});
@@ -609,8 +609,8 @@ describe('/asset', () => {
.send({ ids: [uuidDto.notFound] })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('Not found or no asset.delete access'));
expect(status).toBe(403);
expect(body).toEqual(errorDto.noPermission);
});
it('should move an asset to trash', async () => {
+5 -5
View File
@@ -46,7 +46,7 @@ describe('/memories', () => {
const { status, body } = await request(app)
.get(`/memories/${userMemory.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400);
expect(status).toBe(403);
expect(body).toEqual(errorDto.noPermission);
});
@@ -65,7 +65,7 @@ describe('/memories', () => {
.put(`/memories/${userMemory.id}`)
.send({ isSaved: true })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400);
expect(status).toBe(403);
expect(body).toEqual(errorDto.noPermission);
});
@@ -91,7 +91,7 @@ describe('/memories', () => {
.put(`/memories/${userMemory.id}/assets`)
.send({ ids: [userAsset1.id] })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400);
expect(status).toBe(403);
expect(body).toEqual(errorDto.noPermission);
});
@@ -126,7 +126,7 @@ describe('/memories', () => {
.delete(`/memories/${userMemory.id}/assets`)
.send({ ids: [userAsset1.id] })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400);
expect(status).toBe(403);
expect(body).toEqual(errorDto.noPermission);
});
@@ -160,7 +160,7 @@ describe('/memories', () => {
const { status, body } = await request(app)
.delete(`/memories/${userMemory.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400);
expect(status).toBe(403);
expect(body).toEqual(errorDto.noPermission);
});
+2 -2
View File
@@ -52,8 +52,8 @@ describe('/sessions', () => {
const { status, body } = await request(app)
.delete(`/sessions/${uuidDto.notFound}`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('Not found or no authDevice.delete access'));
expect(status).toBe(403);
expect(body).toEqual(errorDto.noPermission);
});
it('should logout a device', async () => {
+1 -1
View File
@@ -61,7 +61,7 @@ describe('/stacks', () => {
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ assetIds: [asset.id, user2Asset.id] });
expect(status).toBe(400);
expect(status).toBe(403);
expect(body).toEqual(errorDto.noPermission);
});
+16 -16
View File
@@ -51,7 +51,7 @@ describe('/tags', () => {
const { secret } = await utils.createApiKey(user.accessToken, [Permission.AssetRead]);
const { status, body } = await request(app).post('/tags').set('x-api-key', secret).send({ name: 'TagA' });
expect(status).toBe(403);
expect(body).toEqual(errorDto.missingPermission('tag.create'));
expect(body).toEqual(errorDto.missingPermission);
});
it('should work with tag.create', async () => {
@@ -127,7 +127,7 @@ describe('/tags', () => {
const { secret } = await utils.createApiKey(user.accessToken, [Permission.AssetRead]);
const { status, body } = await request(app).get('/tags').set('x-api-key', secret);
expect(status).toBe(403);
expect(body).toEqual(errorDto.missingPermission('tag.read'));
expect(body).toEqual(errorDto.missingPermission);
});
it('should start off empty', async () => {
@@ -179,7 +179,7 @@ describe('/tags', () => {
const { secret } = await utils.createApiKey(user.accessToken, [Permission.AssetRead]);
const { status, body } = await request(app).put('/tags').set('x-api-key', secret).send({ name: 'TagA' });
expect(status).toBe(403);
expect(body).toEqual(errorDto.missingPermission('tag.create'));
expect(body).toEqual(errorDto.missingPermission);
});
it('should upsert tags', async () => {
@@ -226,7 +226,7 @@ describe('/tags', () => {
.set('x-api-key', secret)
.send({ assetIds: [], tagIds: [] });
expect(status).toBe(403);
expect(body).toEqual(errorDto.missingPermission('tag.asset'));
expect(body).toEqual(errorDto.missingPermission);
});
it('should skip assets that are not owned by the user', async () => {
@@ -290,7 +290,7 @@ describe('/tags', () => {
const { status, body } = await request(app)
.get(`/tags/${tag.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400);
expect(status).toBe(403);
expect(body).toEqual(errorDto.noPermission);
});
@@ -301,7 +301,7 @@ describe('/tags', () => {
.set('x-api-key', secret)
.send({ assetIds: [], tagIds: [] });
expect(status).toBe(403);
expect(body).toEqual(errorDto.missingPermission('tag.read'));
expect(body).toEqual(errorDto.missingPermission);
});
it('should require a valid uuid', async () => {
@@ -362,7 +362,7 @@ describe('/tags', () => {
.put(`/tags/${tag.id}`)
.send({ color: '#000000' })
.set('Authorization', `Bearer ${user.accessToken}`);
expect(status).toBe(400);
expect(status).toBe(403);
expect(body).toEqual(errorDto.noPermission);
});
@@ -374,7 +374,7 @@ describe('/tags', () => {
.set('x-api-key', secret)
.send({ color: '#000000' });
expect(status).toBe(403);
expect(body).toEqual(errorDto.missingPermission('tag.update'));
expect(body).toEqual(errorDto.missingPermission);
});
it('should update a tag', async () => {
@@ -410,7 +410,7 @@ describe('/tags', () => {
const { status, body } = await request(app)
.delete(`/tags/${tag.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400);
expect(status).toBe(403);
expect(body).toEqual(errorDto.noPermission);
});
@@ -419,7 +419,7 @@ describe('/tags', () => {
const { secret } = await utils.createApiKey(user.accessToken, [Permission.AssetRead]);
const { status, body } = await request(app).delete(`/tags/${tag.id}`).set('x-api-key', secret);
expect(status).toBe(403);
expect(body).toEqual(errorDto.missingPermission('tag.delete'));
expect(body).toEqual(errorDto.missingPermission);
});
it('should require a valid uuid', async () => {
@@ -478,7 +478,7 @@ describe('/tags', () => {
.put(`/tags/${tag.id}/assets`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ ids: [userAsset.id] });
expect(status).toBe(400);
expect(status).toBe(403);
expect(body).toEqual(errorDto.noPermission);
});
@@ -490,7 +490,7 @@ describe('/tags', () => {
.set('x-api-key', secret)
.send({ ids: [userAsset.id] });
expect(status).toBe(403);
expect(body).toEqual(errorDto.missingPermission('tag.asset'));
expect(body).toEqual(errorDto.missingPermission);
});
it('should be able to tag own asset', async () => {
@@ -511,8 +511,8 @@ describe('/tags', () => {
.set('Authorization', `Bearer ${user.accessToken}`)
.send({ ids: [userAsset.id] });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('Not found or no tag.asset access'));
expect(status).toBe(403);
expect(body).toEqual(errorDto.noPermission);
});
it('should add duplicate assets only once', async () => {
@@ -552,7 +552,7 @@ describe('/tags', () => {
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ ids: [userAsset.id] });
expect(status).toBe(400);
expect(status).toBe(403);
expect(body).toEqual(errorDto.noPermission);
});
@@ -564,7 +564,7 @@ describe('/tags', () => {
.set('x-api-key', secret)
.send({ ids: [userAsset.id] });
expect(status).toBe(403);
expect(body).toEqual(errorDto.missingPermission('tag.asset'));
expect(body).toEqual(errorDto.missingPermission);
});
it('should be able to remove own asset from own tag', async () => {