feat: get metadata about the current api key (#21027)
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
import { BadRequestException, ForbiddenException } from '@nestjs/common';
|
||||
import { Permission } from 'src/enum';
|
||||
import { ApiKeyService } from 'src/services/api-key.service';
|
||||
import { factory, newUuid } from 'test/small.factory';
|
||||
@@ -134,6 +134,41 @@ describe(ApiKeyService.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMine', () => {
|
||||
it('should not work with a session token', async () => {
|
||||
const session = factory.session();
|
||||
const auth = factory.auth({ session });
|
||||
|
||||
mocks.apiKey.getById.mockResolvedValue(void 0);
|
||||
|
||||
await expect(sut.getMine(auth)).rejects.toBeInstanceOf(ForbiddenException);
|
||||
|
||||
expect(mocks.apiKey.getById).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should throw an error if the key is not found', async () => {
|
||||
const apiKey = factory.authApiKey();
|
||||
const auth = factory.auth({ apiKey });
|
||||
|
||||
mocks.apiKey.getById.mockResolvedValue(void 0);
|
||||
|
||||
await expect(sut.getMine(auth)).rejects.toBeInstanceOf(BadRequestException);
|
||||
|
||||
expect(mocks.apiKey.getById).toHaveBeenCalledWith(auth.user.id, apiKey.id);
|
||||
});
|
||||
|
||||
it('should get a key by id', async () => {
|
||||
const auth = factory.auth();
|
||||
const apiKey = factory.apiKey({ userId: auth.user.id });
|
||||
|
||||
mocks.apiKey.getById.mockResolvedValue(apiKey);
|
||||
|
||||
await sut.getById(auth, apiKey.id);
|
||||
|
||||
expect(mocks.apiKey.getById).toHaveBeenCalledWith(auth.user.id, apiKey.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getById', () => {
|
||||
it('should throw an error if the key is not found', async () => {
|
||||
const auth = factory.auth();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { BadRequestException, ForbiddenException, Injectable } from '@nestjs/common';
|
||||
import { ApiKey } from 'src/database';
|
||||
import { APIKeyCreateDto, APIKeyCreateResponseDto, APIKeyResponseDto, APIKeyUpdateDto } from 'src/dtos/api-key.dto';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
@@ -46,6 +46,19 @@ export class ApiKeyService extends BaseService {
|
||||
await this.apiKeyRepository.delete(auth.user.id, id);
|
||||
}
|
||||
|
||||
async getMine(auth: AuthDto): Promise<APIKeyResponseDto> {
|
||||
if (!auth.apiKey) {
|
||||
throw new ForbiddenException('Not authenticated with an API Key');
|
||||
}
|
||||
|
||||
const key = await this.apiKeyRepository.getById(auth.user.id, auth.apiKey.id);
|
||||
if (!key) {
|
||||
throw new BadRequestException('API Key not found');
|
||||
}
|
||||
|
||||
return this.map(key);
|
||||
}
|
||||
|
||||
async getById(auth: AuthDto, id: string): Promise<APIKeyResponseDto> {
|
||||
const key = await this.apiKeyRepository.getById(auth.user.id, id);
|
||||
if (!key) {
|
||||
|
||||
@@ -518,6 +518,20 @@ describe(AuthService.name, () => {
|
||||
await expect(result).rejects.toThrow('Missing required permission: all');
|
||||
});
|
||||
|
||||
it('should not require any permission when metadata is set to `false`', async () => {
|
||||
const authUser = factory.authUser();
|
||||
const authApiKey = factory.authApiKey({ permissions: [Permission.ActivityRead] });
|
||||
|
||||
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', permission: false },
|
||||
});
|
||||
await expect(result).resolves.toEqual({ user: authUser, apiKey: expect.objectContaining(authApiKey) });
|
||||
});
|
||||
|
||||
it('should return an auth dto', async () => {
|
||||
const authUser = factory.authUser();
|
||||
const authApiKey = factory.authApiKey({ permissions: [Permission.All] });
|
||||
|
||||
@@ -48,7 +48,8 @@ export type ValidateRequest = {
|
||||
metadata: {
|
||||
sharedLinkRoute: boolean;
|
||||
adminRoute: boolean;
|
||||
permission?: Permission;
|
||||
/** `false` explicitly means no permission is required, which otherwise defaults to `all` */
|
||||
permission?: Permission | false;
|
||||
uri: string;
|
||||
};
|
||||
};
|
||||
@@ -187,7 +188,11 @@ export class AuthService extends BaseService {
|
||||
throw new ForbiddenException('Forbidden');
|
||||
}
|
||||
|
||||
if (authDto.apiKey && !isGranted({ requested: [requestedPermission], current: authDto.apiKey.permissions })) {
|
||||
if (
|
||||
authDto.apiKey &&
|
||||
requestedPermission !== false &&
|
||||
!isGranted({ requested: [requestedPermission], current: authDto.apiKey.permissions })
|
||||
) {
|
||||
throw new ForbiddenException(`Missing required permission: ${requestedPermission}`);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user