Skip to content

Commit

Permalink
fix: 🐞 secrets are generated with JWT
Browse files Browse the repository at this point in the history
secrets are generated with JWT
  • Loading branch information
tal-rofe committed Jul 29, 2022
1 parent dc80bcf commit 3299742
Show file tree
Hide file tree
Showing 11 changed files with 79 additions and 19 deletions.
1 change: 1 addition & 0 deletions apps/backend/@types/global/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ declare global {
readonly GITHUB_OAUTH_REDIRECT_URI: string;
readonly MIXPANEL_TOKEN: string;
readonly FRONTEND_URL: string;
readonly CLI_TOKEN_JWT_KEY: string;
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion apps/backend/envs/.env.development
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ GITHUB_OAUTH_CLIENT_ID="DUMMY"
GITHUB_OAUTH_CLIENT_SECRET="DUMMY"
GITHUB_OAUTH_REDIRECT_URI="http://localhost:3000/user/auth/github-redirect"
MIXPANEL_TOKEN="XOMBILLAH"
FRONTEND_URL="http://localhost:8080"
FRONTEND_URL="http://localhost:8080"
CLI_TOKEN_JWT_KEY="JWT"
1 change: 1 addition & 0 deletions apps/backend/src/config/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const EnvConfiguration = (): IEnvironment => ({
githubOAuthRedirectUri: process.env.GITHUB_OAUTH_REDIRECT_URI,
mixpanelToken: process.env.MIXPANEL_TOKEN,
frontendUrl: process.env.FRONTEND_URL,
cliTokenJwtKey: process.env.CLI_TOKEN_JWT_KEY,
});

export default EnvConfiguration;
1 change: 1 addition & 0 deletions apps/backend/src/config/env.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ export interface IEnvironment {
readonly githubOAuthRedirectUri: string;
readonly mixpanelToken: string;
readonly frontendUrl: string;
readonly cliTokenJwtKey: string;
}
3 changes: 3 additions & 0 deletions apps/backend/src/config/env.validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ class EnvironmentVariables {

@IsString()
public FRONTEND_URL!: string;

@IsString()
public CLI_TOKEN_JWT_KEY!: string;
}

export const validate = (config: Record<string, unknown>) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { IsNumber, IsString, MinLength } from 'class-validator';
import { IsISO8601, IsString, MinLength } from 'class-validator';

import { IsNullable } from '@/decorators/is-nullable.decorator';

import { IsValidExpiration } from '../decorators/valid-expiration.decorator';

export class CreateSecretDto {
@IsString()
@MinLength(1)
readonly label!: string;

@IsNumber()
@IsISO8601()
@IsValidExpiration()
@IsNullable()
readonly expiration!: number | null;
readonly expiration!: string | null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Body, Controller, Logger, Post } from '@nestjs/common';
import { QueryBus } from '@nestjs/cqrs';

import { CurrentUserId } from '@/decorators/current-user-id.decorator';
import { CurrentUserEmail } from '@/decorators/current-user-email.decorator';

import Routes from './secrets.routes';
import type { ICreateClientSecret } from './interfaces/responses';
Expand All @@ -17,18 +18,18 @@ export class CreateController {
@Post(Routes.CREATE)
public async create(
@CurrentUserId() userId: string,
@CurrentUserEmail() userEmail: string,
@Body() createSecretDto: CreateSecretDto,
): Promise<ICreateClientSecret> {
this.logger.log(`Will try to create a client secret with to user with an Id: "${userId}"`);

let expirationDate: Date | null = null;

if (createSecretDto.expiration !== null) {
expirationDate = new Date(createSecretDto.expiration);
}

const secret = await this.queryBus.execute<CreateSecretContract, string>(
new CreateSecretContract(userId, createSecretDto.label, expirationDate),
new CreateSecretContract(
userId,
userEmail,
createSecretDto.label,
createSecretDto.expiration ? new Date(createSecretDto.expiration).getTime() : null,
),
);

this.logger.log('Successfully deleted a client secret');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { registerDecorator } from 'class-validator';

export function IsValidExpiration() {
return function (object: object, propertyName: string) {
registerDecorator({
name: 'validExpiration',
target: object.constructor,
propertyName: propertyName,
validator: {
validate(value: unknown) {
if (typeof value !== 'string') {
return false;
}

const currentDate = new Date();
const inputDate = new Date(value);

if (isNaN(inputDate.getTime())) {
return false;
}

return inputDate >= currentDate;
},
},
});
};
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
export class CreateSecretContract {
constructor(
public readonly userId: string,
public readonly email: string,
public readonly label: string,
public readonly expiration: Date | null,
public readonly expiration: number | null,
) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,19 @@ export class CreateSecretHandler implements IQueryHandler<CreateSecretContract>
) {}

async execute(contract: CreateSecretContract) {
const secret = this.secretsService.generateSecret();
const secret = await this.secretsService.generateSecret(
contract.userId,
contract.email,
contract.expiration,
);

const expirationDate = contract.expiration ? new Date(contract.expiration) : null;

await this.dbClientSecretService.createSecret(
contract.userId,
secret,
contract.label,
contract.expiration,
expirationDate,
);

return secret;
Expand Down
25 changes: 20 additions & 5 deletions apps/backend/src/modules/user/modules/secrets/secrets.service.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
import crypto from 'crypto';

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';

import type { IEnvironment } from '@/config/env.interface';

@Injectable()
export class SecretsService {
public generateSecret() {
const secret = crypto.randomUUID();
constructor(
private readonly configService: ConfigService<IEnvironment, true>,
private readonly jwtService: JwtService,
) {}

public async generateSecret(userId: string, email: string, expiration: number | null) {
const jwtPayload = {
sub: userId,
email,
};

const token = await this.jwtService.signAsync(jwtPayload, {
secret: this.configService.get('cliTokenJwtKey', { infer: true }),
...(expiration ? { expiresIn: expiration } : {}),
});

return secret;
return token;
}
}

0 comments on commit 3299742

Please sign in to comment.