From bc8cb9b671412ff8aa2999a74ee45d5ff272b058 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Wed, 23 Jul 2025 16:56:38 -0400 Subject: [PATCH] fix: default route permission (#20113) --- server/src/services/auth.service.spec.ts | 32 ++++++++++++++++++------ server/src/services/auth.service.ts | 7 +++--- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/server/src/services/auth.service.spec.ts b/server/src/services/auth.service.spec.ts index 129877bbdd..9f678162c6 100644 --- a/server/src/services/auth.service.spec.ts +++ b/server/src/services/auth.service.spec.ts @@ -459,18 +459,34 @@ describe(AuthService.name, () => { mocks.apiKey.getKey.mockResolvedValue({ ...authApiKey, user: authUser }); - await expect( - sut.authenticate({ - headers: { 'x-api-key': 'auth_token' }, - queryParams: {}, - metadata: { adminRoute: false, sharedLinkRoute: false, uri: 'test', permission: Permission.AssetRead }, - }), - ).rejects.toBeInstanceOf(ForbiddenException); + const result = sut.authenticate({ + headers: { 'x-api-key': 'auth_token' }, + queryParams: {}, + metadata: { adminRoute: false, sharedLinkRoute: false, uri: 'test', permission: Permission.AssetRead }, + }); + + await expect(result).rejects.toBeInstanceOf(ForbiddenException); + await expect(result).rejects.toThrow('Missing required permission: asset.read'); + }); + + it('should default to requiring the all permission when omitted', async () => { + const authUser = factory.authUser(); + const authApiKey = factory.authApiKey({ permissions: [Permission.AssetRead] }); + + mocks.apiKey.getKey.mockResolvedValue({ ...authApiKey, user: authUser }); + + const result = sut.authenticate({ + headers: { 'x-api-key': 'auth_token' }, + queryParams: {}, + metadata: { adminRoute: false, sharedLinkRoute: false, uri: 'test' }, + }); + await expect(result).rejects.toBeInstanceOf(ForbiddenException); + await expect(result).rejects.toThrow('Missing required permission: all'); }); it('should return an auth dto', async () => { const authUser = factory.authUser(); - const authApiKey = factory.authApiKey({ permissions: [] }); + const authApiKey = factory.authApiKey({ permissions: [Permission.All] }); mocks.apiKey.getKey.mockResolvedValue({ ...authApiKey, user: authUser }); diff --git a/server/src/services/auth.service.ts b/server/src/services/auth.service.ts index a5b0de25cd..f757bf5a3e 100644 --- a/server/src/services/auth.service.ts +++ b/server/src/services/auth.service.ts @@ -174,7 +174,8 @@ export class AuthService extends BaseService { async authenticate({ headers, queryParams, metadata }: ValidateRequest): Promise { const authDto = await this.validate({ headers, queryParams }); - const { adminRoute, sharedLinkRoute, permission, uri } = metadata; + const { adminRoute, sharedLinkRoute, uri } = metadata; + const requestedPermission = metadata.permission ?? Permission.All; if (!authDto.user.isAdmin && adminRoute) { this.logger.warn(`Denied access to admin only route: ${uri}`); @@ -186,8 +187,8 @@ export class AuthService extends BaseService { throw new ForbiddenException('Forbidden'); } - if (authDto.apiKey && permission && !isGranted({ requested: [permission], current: authDto.apiKey.permissions })) { - throw new ForbiddenException(`Missing required permission: ${permission}`); + if (authDto.apiKey && !isGranted({ requested: [requestedPermission], current: authDto.apiKey.permissions })) { + throw new ForbiddenException(`Missing required permission: ${requestedPermission}`); } return authDto;