Skip to content

Commit

Permalink
Merge branch 'main' into fix/api-script-e2e
Browse files Browse the repository at this point in the history
  • Loading branch information
scopsy authored Sep 28, 2022
2 parents 8a760fb + ce2f13f commit bb1111d
Show file tree
Hide file tree
Showing 56 changed files with 1,549 additions and 436 deletions.
6 changes: 6 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,5 +90,11 @@ module.exports = {
leadingUnderscore: 'allow',
},
],
'prettier/prettier': [
'error',
{
endOfLine: 'auto',
},
],
},
};
31 changes: 31 additions & 0 deletions apps/api/e2e/api/environment/regenerate-api-keys.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { UserSession } from '@novu/testing';
import { expect } from 'chai';

describe('Environment - Regenerate Api Key', async () => {
let session: UserSession;

before(async () => {
session = new UserSession();
await session.initialize();
});

it('should regenerate an Api Key', async () => {
const {
body: { data: oldApiKeys },
} = await session.testAgent.get('/v1/environments/api-keys').send({});
const oldApiKey = oldApiKeys[0].key;

const {
body: { data: newApiKeys },
} = await session.testAgent.post('/v1/environments/api-keys/regenerate').send({});
const newApiKey = newApiKeys[0].key;

expect(oldApiKey).to.not.equal(newApiKey);

const {
body: { data: organizations },
} = await session.testAgent.get('/v1/organizations').send({});

expect(organizations).not.to.be.empty;
});
});
20 changes: 20 additions & 0 deletions apps/api/src/app/environments/environments.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiTags } from '@nestj
import { ApiKey } from '../shared/dtos/api-key';
import { EnvironmentResponseDto } from './dtos/environment-response.dto';
import { ExternalApiAccessible } from '../auth/framework/external-api.decorator';
import { RegenerateApiKeys } from './usecases/regenerate-api-keys/regenerate-api-keys.usecase';

@Controller('/environments')
@UseInterceptors(ClassSerializerInterceptor)
Expand All @@ -35,6 +36,7 @@ export class EnvironmentsController {
constructor(
private createEnvironmentUsecase: CreateEnvironment,
private getApiKeysUsecase: GetApiKeys,
private regenerateApiKeysUsecase: RegenerateApiKeys,
private getEnvironmentUsecase: GetEnvironment,
private getMyEnvironmentsUsecase: GetMyEnvironments,
private updateWidgetSettingsUsecase: UpdateWidgetSettings
Expand Down Expand Up @@ -116,6 +118,24 @@ export class EnvironmentsController {
return await this.getApiKeysUsecase.execute(command);
}

@Post('/api-keys/regenerate')
@ApiOperation({
summary: 'Regenerate api keys',
})
@ApiOkResponse({
type: [ApiKey],
})
@ExternalApiAccessible()
async regenerateOrganizationApiKeys(@UserSession() user: IJwtPayload): Promise<ApiKey[]> {
const command = GetApiKeysCommand.create({
userId: user._id,
organizationId: user.organizationId,
environmentId: user.environmentId,
});

return await this.regenerateApiKeysUsecase.execute(command);
}

@Put('/widget/settings')
@ApiOperation({
summary: 'Update widget settings',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,33 @@
import { Injectable } from '@nestjs/common';
import { EnvironmentRepository } from '@novu/dal';
import * as hat from 'hat';
import { nanoid } from 'nanoid';

import { CreateEnvironmentCommand } from './create-environment.command';

import { GenerateUniqueApiKey } from '../generate-unique-api-key/generate-unique-api-key.usecase';
// eslint-disable-next-line max-len
import { CreateNotificationGroupCommand } from '../../../notification-groups/usecases/create-notification-group/create-notification-group.command';
import { CreateNotificationGroup } from '../../../notification-groups/usecases/create-notification-group/create-notification-group.usecase';
import { CreateEnvironmentCommand } from './create-environment.command';

@Injectable()
export class CreateEnvironment {
constructor(
private environmentRepository: EnvironmentRepository,
private createNotificationGroup: CreateNotificationGroup
private createNotificationGroup: CreateNotificationGroup,
private generateUniqueApiKey: GenerateUniqueApiKey
) {}

async execute(command: CreateEnvironmentCommand) {
const key = await this.generateUniqueApiKey.execute();

const environment = await this.environmentRepository.create({
_organizationId: command.organizationId,
name: command.name,
identifier: nanoid(12),
_parentId: command.parentEnvironmentId,
apiKeys: [
{
key: hat(),
key,
_userId: command.userId,
},
],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { EnvironmentRepository } from '@novu/dal';
import { InternalServerErrorException } from '@nestjs/common';
import { expect } from 'chai';
import * as sinon from 'sinon';

import { GenerateUniqueApiKey } from './generate-unique-api-key.usecase';

const environmentRepository = new EnvironmentRepository();
const generateUniqueApiKey = new GenerateUniqueApiKey(environmentRepository);

let generateApiKeyStub;
let findByApiKeyStub;
describe('Generate Unique Api Key', () => {
beforeEach(() => {
findByApiKeyStub = sinon.stub(environmentRepository, 'findByApiKey');
generateApiKeyStub = sinon.stub(generateUniqueApiKey, 'generateApiKey' as any);
});

afterEach(() => {
findByApiKeyStub.restore();
generateApiKeyStub.restore();
});

it('should generate an API key for the environment without any clashing', async () => {
const expectedApiKey = 'expected-api-key';
generateApiKeyStub.onFirstCall().returns(expectedApiKey);

const apiKey = await generateUniqueApiKey.execute();

expect(typeof apiKey).to.be.string;
expect(apiKey).to.be.equal(expectedApiKey);
});

it('should generate a different valid API key after first one clashes with an existing one', async () => {
const clashingApiKey = 'clashing-api-key';
const expectedApiKey = 'expected-api-key';
generateApiKeyStub.onFirstCall().returns(clashingApiKey);
generateApiKeyStub.onSecondCall().returns(expectedApiKey);
findByApiKeyStub.onFirstCall().returns({ key: clashingApiKey });
findByApiKeyStub.onSecondCall().returns(undefined);

const apiKey = await generateUniqueApiKey.execute();
expect(typeof apiKey).to.be.string;
expect(apiKey).to.be.equal(expectedApiKey);
});

it('should throw an error if the generation clashes 3 times', async () => {
const clashingApiKey = 'clashing-api-key';
generateApiKeyStub.onFirstCall().returns(clashingApiKey);
generateApiKeyStub.onSecondCall().returns(clashingApiKey);
generateApiKeyStub.onThirdCall().returns(clashingApiKey);
findByApiKeyStub.onFirstCall().returns({ key: clashingApiKey });
findByApiKeyStub.onSecondCall().returns({ key: clashingApiKey });
findByApiKeyStub.onThirdCall().returns({ key: clashingApiKey });

try {
await generateUniqueApiKey.execute();
throw new Error('Should not reach here');
} catch (e) {
expect(e).to.be.instanceOf(InternalServerErrorException);
expect(e.message).to.eql('Clashing of the API key generation');
}
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Injectable, InternalServerErrorException } from '@nestjs/common';
import { EnvironmentRepository } from '@novu/dal';
import * as hat from 'hat';

const API_KEY_GENERATION_MAX_RETRIES = 3;

@Injectable()
export class GenerateUniqueApiKey {
constructor(private environmentRepository: EnvironmentRepository) {}

async execute(): Promise<string> {
let apiKey: string;
let count = 0;
let isApiKeyUsed = true;
while (isApiKeyUsed) {
apiKey = this.generateApiKey();
const environment = await this.environmentRepository.findByApiKey(apiKey);
isApiKeyUsed = environment ? true : false;
count += 1;

if (count === API_KEY_GENERATION_MAX_RETRIES) {
const errorMessage = 'Clashing of the API key generation';
throw new InternalServerErrorException(new Error(errorMessage), errorMessage);
}
}

return apiKey;
}

/**
* Extracting the generation functionality so it can be stubbed for functional testing
*
* @requires hat
* @todo Dependency is no longer accessible to source code due of removal from Github. Consider look for an alternative.
*/
private generateApiKey(): string {
return hat();
}
}
4 changes: 4 additions & 0 deletions apps/api/src/app/environments/usecases/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { CreateEnvironment } from './create-environment/create-environment.usecase';
import { GenerateUniqueApiKey } from './generate-unique-api-key/generate-unique-api-key.usecase';
import { GetApiKeys } from './get-api-keys/get-api-keys.usecase';
import { RegenerateApiKeys } from './regenerate-api-keys/regenerate-api-keys.usecase';
import { GetEnvironment } from './get-environment';
import { GetMyEnvironments } from './get-my-environments/get-my-environments.usecase';
import { UpdateWidgetSettings } from './update-widget-settings/update-widget-settings.usecase';

export const USE_CASES = [
//
CreateEnvironment,
GenerateUniqueApiKey,
GetApiKeys,
RegenerateApiKeys,
GetEnvironment,
GetMyEnvironments,
UpdateWidgetSettings,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Injectable } from '@nestjs/common';
import { IApiKey, EnvironmentRepository } from '@novu/dal';
import { ApiException } from '../../../shared/exceptions/api.exception';
import { GenerateUniqueApiKey } from '../generate-unique-api-key/generate-unique-api-key.usecase';
import { GetApiKeysCommand } from '../get-api-keys/get-api-keys.command';

@Injectable()
export class RegenerateApiKeys {
constructor(
private environmentRepository: EnvironmentRepository,
private generateUniqueApiKey: GenerateUniqueApiKey
) {}

async execute(command: GetApiKeysCommand): Promise<IApiKey[]> {
const environment = await this.environmentRepository.findById(command.environmentId);

if (!environment) {
throw new ApiException(`Environment id: ${command.environmentId} not found`);
}

const key = await this.generateUniqueApiKey.execute();

return await this.environmentRepository.updateApiKey(command.environmentId, key, command.userId);
}
}
Loading

0 comments on commit bb1111d

Please sign in to comment.