Skip to content

Commit 578e41f

Browse files
committed
add endpoint to update api key with provided value
1 parent 248adfc commit 578e41f

File tree

6 files changed

+113
-0
lines changed

6 files changed

+113
-0
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { ApiProperty } from '@nestjs/swagger';
2+
import { IsString } from 'class-validator';
3+
4+
export class UpdateApiKeyRequestDto {
5+
@ApiProperty()
6+
@IsString()
7+
apiKey: string;
8+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { NOVU_ENCRYPTION_SUB_MASK } from '@novu/shared';
2+
import { UserSession } from '@novu/testing';
3+
import { expect } from 'chai';
4+
5+
describe('Environment - Update Api Key #novu-v0-os', async () => {
6+
let session: UserSession;
7+
8+
before(async () => {
9+
session = new UserSession();
10+
await session.initialize();
11+
});
12+
13+
it('should update an Api Key', async () => {
14+
const {
15+
body: { data: oldApiKeys },
16+
} = await session.testAgent.get('/v1/environments/api-keys').send({});
17+
const oldApiKey = oldApiKeys[0].key;
18+
expect(oldApiKey).to.not.contains(NOVU_ENCRYPTION_SUB_MASK);
19+
20+
const newKey = 'my-new-api-key-123456-of-32chars';
21+
const {
22+
body: { data: newApiKeys },
23+
} = await session.testAgent.post('/v1/environments/api-keys/update').send({
24+
apiKey: newKey,
25+
});
26+
27+
const newApiKey = newApiKeys[0].key;
28+
expect(newApiKey).to.equals(newKey);
29+
});
30+
});

apps/api/src/app/environments-v1/environments-v1.controller.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ import { GetMyEnvironments } from './usecases/get-my-environments/get-my-environ
4848
import { RegenerateApiKeys } from './usecases/regenerate-api-keys/regenerate-api-keys.usecase';
4949
import { UpdateEnvironmentCommand } from './usecases/update-environment/update-environment.command';
5050
import { UpdateEnvironment } from './usecases/update-environment/update-environment.usecase';
51+
import { UpdateApiKeyCommand } from './usecases/update-api-key/update-api-key.command';
52+
import { UpdateApiKeyRequestDto } from './dtos/update-api-key-request.dto';
53+
import { UpdateApiKey } from './usecases/update-api-key/update-api-key.usecase';
5154

5255
/**
5356
* @deprecated use EnvironmentsControllerV2
@@ -63,6 +66,7 @@ export class EnvironmentsControllerV1 {
6366
private updateEnvironmentUsecase: UpdateEnvironment,
6467
private getApiKeysUsecase: GetApiKeys,
6568
private regenerateApiKeysUsecase: RegenerateApiKeys,
69+
private updateApiKeyUsecase: UpdateApiKey,
6670
private getEnvironmentUsecase: GetEnvironment,
6771
private getMyEnvironmentsUsecase: GetMyEnvironments,
6872
private deleteEnvironmentUsecase: DeleteEnvironment,
@@ -209,6 +213,24 @@ export class EnvironmentsControllerV1 {
209213
return await this.regenerateApiKeysUsecase.execute(command);
210214
}
211215

216+
@Post('/api-keys/update')
217+
@ApiResponse(ApiKey, 201, true)
218+
@ApiExcludeEndpoint()
219+
@RequirePermissions(PermissionsEnum.API_KEY_WRITE)
220+
async updateOrganizationApiKeys(
221+
@UserSession() user: UserSessionData,
222+
@Body() payload: UpdateApiKeyRequestDto
223+
): Promise<ApiKey[]> {
224+
const command = UpdateApiKeyCommand.create({
225+
userId: user._id,
226+
organizationId: user.organizationId,
227+
environmentId: user.environmentId,
228+
apiKey: payload.apiKey,
229+
});
230+
231+
return await this.updateApiKeyUsecase.execute(command);
232+
}
233+
212234
@Delete('/:environmentId')
213235
@ApiOperation({
214236
summary: 'Delete an environment',

apps/api/src/app/environments-v1/usecases/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { GetEnvironment } from './get-environment';
77
import { GetMyEnvironments } from './get-my-environments/get-my-environments.usecase';
88
import { RegenerateApiKeys } from './regenerate-api-keys/regenerate-api-keys.usecase';
99
import { UpdateEnvironment } from './update-environment/update-environment.usecase';
10+
import { UpdateApiKey } from './update-api-key/update-api-key.usecase';
1011

1112
export const USE_CASES = [
1213
GetMxRecord,
@@ -15,6 +16,7 @@ export const USE_CASES = [
1516
GenerateUniqueApiKey,
1617
GetApiKeys,
1718
RegenerateApiKeys,
19+
UpdateApiKey,
1820
GetEnvironment,
1921
GetMyEnvironments,
2022
DeleteEnvironment,
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { EnvironmentWithUserCommand } from '../../../shared/commands/project.command';
2+
import { IsNotEmpty, IsString } from 'class-validator';
3+
4+
export class UpdateApiKeyCommand extends EnvironmentWithUserCommand {
5+
@IsNotEmpty()
6+
@IsString()
7+
readonly apiKey: string;
8+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { BadRequestException, Injectable } from '@nestjs/common';
2+
import { decryptApiKey, encryptApiKey } from '@novu/application-generic';
3+
import { EnvironmentRepository } from '@novu/dal';
4+
import { createHash } from 'crypto';
5+
import { ApiKeyDto } from '../../dtos/api-key.dto';
6+
import { UpdateApiKeyCommand } from './update-api-key.command';
7+
8+
@Injectable()
9+
export class UpdateApiKey {
10+
constructor(
11+
private environmentRepository: EnvironmentRepository,
12+
) {}
13+
14+
async execute(command: UpdateApiKeyCommand): Promise<ApiKeyDto[]> {
15+
const environment = await this.environmentRepository.findOne({ _id: command.environmentId });
16+
17+
if (!environment) {
18+
throw new BadRequestException(`Environment id: ${command.environmentId} not found`);
19+
}
20+
21+
const key = command.apiKey;
22+
if (key.length < 32) {
23+
throw new BadRequestException(`API key must be at least 32 characters long`);
24+
}
25+
26+
const encryptedApiKey = encryptApiKey(key);
27+
const hashedApiKey = createHash('sha256').update(key).digest('hex');
28+
29+
const environments = await this.environmentRepository.updateApiKey(
30+
command.environmentId,
31+
encryptedApiKey,
32+
command.userId,
33+
hashedApiKey
34+
);
35+
36+
return environments.map((item) => {
37+
return {
38+
_userId: item._userId,
39+
key: decryptApiKey(item.key),
40+
};
41+
});
42+
}
43+
}

0 commit comments

Comments
 (0)