Skip to content

Commit

Permalink
feat: add logic to clear otp on create
Browse files Browse the repository at this point in the history
  • Loading branch information
tnramalho committed Dec 24, 2024
1 parent ba85c59 commit 1739655
Show file tree
Hide file tree
Showing 20 changed files with 1,889 additions and 1,837 deletions.
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
import { randomUUID } from 'crypto';
import { Injectable } from '@nestjs/common';
import {
OtpCreateParamsInterface,
ReferenceAssigneeInterface,
ReferenceIdInterface,
} from '@concepta/nestjs-common';
import { OtpCreatableInterface, OtpInterface } from '@concepta/nestjs-common';
import { OtpInterface } from '@concepta/nestjs-common';

import { AuthRecoveryOtpServiceInterface } from '../../interfaces/auth-recovery-otp.service.interface';
import { UserFixture } from '../user/user.fixture';

@Injectable()
export class OtpServiceFixture implements AuthRecoveryOtpServiceInterface {
async create(
_assignment: string,
otp: OtpCreatableInterface,
): Promise<OtpInterface> {
async create({ otp }: OtpCreateParamsInterface): Promise<OtpInterface> {
const { assignee, category, type } = otp;
return {
id: randomUUID(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import { AuthRecoveryUpdatePasswordDto } from './dto/auth-recovery-update-passwo

import { UserEntityFixture } from './__fixtures__/user/entities/user-entity.fixture';
import { AppModuleDbFixture } from './__fixtures__/app.module.db.fixture';
import { HttpAdapterHost } from '@nestjs/core';
import { ExceptionsFilter } from '@concepta/nestjs-exception';

describe(AuthRecoveryController, () => {
let app: INestApplication;
Expand All @@ -32,6 +34,9 @@ describe(AuthRecoveryController, () => {
imports: [AppModuleDbFixture],
}).compile();
app = moduleFixture.createNestApplication();
const exceptionsFilter = app.get(HttpAdapterHost);
app.useGlobalFilters(new ExceptionsFilter(exceptionsFilter));

await app.init();

otpService = moduleFixture.get<OtpService>(OtpService);
Expand Down Expand Up @@ -82,6 +87,21 @@ describe(AuthRecoveryController, () => {
await validateRecoverPassword(app, user);
});

it('GET auth/recovery/passcode/{passcode} fail after create', async () => {
const user = await getFirstUser(app);

const otpCreateDto = await createOtp(settings, otpService, user.id);
// this should clear old otp
await createOtp(settings, otpService, user.id, true);

const { passcode } = otpCreateDto;

// should fail
await supertest(app.getHttpServer())
.get(`/auth/recovery/passcode/${passcode}`)
.expect(400);
});

it('POST auth/recovery/password', async () => {
const user = await getFirstUser(app);

Expand Down Expand Up @@ -127,15 +147,20 @@ const createOtp = async (
config: AuthRecoverySettingsInterface,
otpService: OtpService,
userId: string,
clearOnCreate?: boolean,
): Promise<OtpInterface> => {
const { category, assignment, type, expiresIn } = config.otp;

return await otpService.create(assignment, {
category,
type,
expiresIn,
assignee: {
id: userId,
return await otpService.create({
assignment,
otp: {
category,
type,
expiresIn,
assignee: {
id: userId,
},
},
clearOnCreate,
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ export const authRecoveryDefaultConfig = registerAs(
category: 'auth-recovery',
type: 'uuid',
expiresIn: '1h',
clearOtpOnCreate: process.env.AUTH_RECOVERY_OTP_CLEAR_ON_CREATE
? process.env.AUTH_RECOVERY_OTP_CLEAR_ON_CREATE === 'true'
: undefined,
},
}),
);
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { RuntimeExceptionOptions } from '@concepta/nestjs-exception';
import { AuthRecoveryException } from './auth-recovery.exception';
import { HttpStatus } from '@nestjs/common';

export class AuthRecoveryOtpInvalidException extends AuthRecoveryException {
constructor(options?: RuntimeExceptionOptions) {
super({
message: `Invalid recovery code provided`,
httpStatus: HttpStatus.BAD_REQUEST,
...options,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { OtpCreatableInterface } from '@concepta/nestjs-common';
export interface AuthRecoveryOtpSettingsInterface
extends Pick<OtpCreatableInterface, 'category' | 'type' | 'expiresIn'> {
assignment: ReferenceAssignment;
clearOtpOnCreate?: boolean;
}

export interface AuthRecoverySettingsInterface {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,12 @@ export class AuthRecoveryService implements AuthRecoveryServiceInterface {
// did we find a user?
if (user) {
// extract required otp properties
const { category, assignment, type, expiresIn } = this.config.otp;
const { category, assignment, type, expiresIn, clearOtpOnCreate } =
this.config.otp;
// create an OTP save it in the database
const otp = await this.otpService.create(
const otp = await this.otpService.create({
assignment,
{
otp: {
category,
type,
expiresIn,
Expand All @@ -91,7 +92,8 @@ export class AuthRecoveryService implements AuthRecoveryServiceInterface {
},
},
queryOptions,
);
clearOnCreate: clearOtpOnCreate,
});

// send en email with a recover OTP
await this.notificationService.sendRecoverPasswordEmail(
Expand Down
1 change: 1 addition & 0 deletions packages/nestjs-common/src/domain/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export { RoleUpdatableInterface } from './role/interfaces/role-updatable.interfa
export { RoleInterface } from './role/interfaces/role.interface';

export { OtpClearInterface } from './otp/interfaces/otp-clear.interface';
export { OtpCreateParamsInterface } from './otp/interfaces/otp-create-params.interface';
export { OtpCreatableInterface } from './otp/interfaces/otp-creatable.interface';
export { OtpCreateInterface } from './otp/interfaces/otp-create.interface';
export { OtpDeleteInterface } from './otp/interfaces/otp-delete.interface';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ReferenceQueryOptionsInterface } from '../../../reference/interfaces/reference-query-options.interface';
import { ReferenceAssignment } from '../../../reference/interfaces/reference.types';
import { OtpCreatableInterface } from './otp-creatable.interface';

export interface OtpCreateParamsInterface<
O extends ReferenceQueryOptionsInterface = ReferenceQueryOptionsInterface,
> {
assignment: ReferenceAssignment;
otp: OtpCreatableInterface;
queryOptions?: O;
clearOnCreate?: boolean;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { ReferenceQueryOptionsInterface } from '../../../reference/interfaces/reference-query-options.interface';
import { ReferenceAssignment } from '../../../reference/interfaces/reference.types';

import { OtpCreatableInterface } from './otp-creatable.interface';
import { OtpCreateParamsInterface } from './otp-create-params.interface';
import { OtpInterface } from './otp.interface';

export interface OtpCreateInterface<
Expand All @@ -10,12 +8,7 @@ export interface OtpCreateInterface<
/**
* Create a otp with a for the given assignee.
*
* @param assignment - The otp assignment
* @param otp - The OTP to create
* @param params - The otp params
*/
create(
assignment: ReferenceAssignment,
otp: OtpCreatableInterface,
options?: O,
): Promise<OtpInterface>;
create(params: OtpCreateParamsInterface<O>): Promise<OtpInterface>;
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
import { randomUUID } from 'crypto';
import { Injectable } from '@nestjs/common';
import {
OtpCreateParamsInterface,
ReferenceAssigneeInterface,
ReferenceIdInterface,
} from '@concepta/nestjs-common';
import { OtpCreatableInterface, OtpInterface } from '@concepta/nestjs-common';
import { OtpInterface } from '@concepta/nestjs-common';

import { InvitationOtpServiceInterface } from '../../interfaces/invitation-otp.service.interface';

import { UserFixture } from '../user/user.fixture';

@Injectable()
export class OtpServiceFixture implements InvitationOtpServiceInterface {
async create(
_assignment: string,
otp: OtpCreatableInterface,
): Promise<OtpInterface> {
async create({ otp }: OtpCreateParamsInterface): Promise<OtpInterface> {
const { assignee, category, type } = otp;
return {
id: randomUUID(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ export const invitationDefaultConfig = registerAs(
assignment: 'user-otp',
type: 'uuid',
expiresIn: '7d',
clearOtpOnCreate: process.env.INVITATION_OTP_CLEAR_ON_CREATE
? process.env.INVITATION_OTP_CLEAR_ON_CREATE === 'true'
: undefined,
},
}),
);
Original file line number Diff line number Diff line change
Expand Up @@ -255,15 +255,20 @@ const createOtp = async (
otpService: OtpService,
user: UserInterface,
category: string,
clearOnCreate?: boolean,
): Promise<OtpInterface> => {
const { assignment, type, expiresIn } = config.otp;

return await otpService.create(assignment, {
category,
type,
expiresIn,
assignee: {
id: user.id,
return await otpService.create({
assignment,
otp: {
category,
type,
expiresIn,
assignee: {
id: user.id,
},
},
clearOnCreate,
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { OtpCreatableInterface } from '@concepta/nestjs-common';
export interface InvitationOtpSettingsInterface
extends Pick<OtpCreatableInterface, 'type' | 'expiresIn'> {
assignment: ReferenceAssignment;
clearOtpOnCreate?: boolean;
}

export interface InvitationSettingsInterface {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,16 +142,21 @@ const createOtp = async (
otpService: OtpService,
user: UserInterface,
category: string,
clearOnCreate?: boolean,
): Promise<OtpInterface> => {
const { assignment, type, expiresIn } = settings.otp;

const otp = await otpService.create(assignment, {
category,
type,
expiresIn,
assignee: {
id: user.id,
const otp = await otpService.create({
assignment,
otp: {
category,
type,
expiresIn,
assignee: {
id: user.id,
},
},
clearOnCreate,
});

expect(otp).toBeTruthy();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@ export class InvitationSendService {
category: string,
queryOptions?: QueryOptionsInterface,
): Promise<void> {
const { assignment, type, expiresIn } = this.settings.otp;
const { assignment, type, expiresIn, clearOtpOnCreate } = this.settings.otp;

// create an OTP for this invite
const otp = await this.otpService.create(
const otp = await this.otpService.create({
assignment,
{
otp: {
category,
type,
expiresIn,
Expand All @@ -51,7 +51,8 @@ export class InvitationSendService {
},
},
queryOptions,
);
clearOnCreate: clearOtpOnCreate,
});

// send the invite email
await this.sendEmail(user.email, code, otp.passcode, otp.expirationDate);
Expand Down
1 change: 1 addition & 0 deletions packages/nestjs-otp/src/config/otp-default.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ export const otpDefaultConfig = registerAs(
validator: uuidValidatorUtil,
},
},
clearOnCreate: process.env.OTP_CLEAR_ON_CREATE == 'true' ? true : false,
}),
);
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ import { OtpTypeServiceInterface } from './otp-types-service.interface';

export interface OtpSettingsInterface {
types: LiteralObject<OtpTypeServiceInterface>;
clearOnCreate: boolean;
}
Loading

0 comments on commit 1739655

Please sign in to comment.