Skip to content

Commit

Permalink
refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
alexeh committed Sep 26, 2024
1 parent dfafdc0 commit 4eafcae
Show file tree
Hide file tree
Showing 8 changed files with 61 additions and 19 deletions.
2 changes: 2 additions & 0 deletions api/src/modules/auth/authentication.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ import { authContract } from '@shared/contracts/auth.contract';
import { AuthenticationService } from '@api/modules/auth/authentication.service';
import { JwtAuthGuard } from '@api/modules/auth/guards/jwt-auth.guard';
import { SignUp } from '@api/modules/auth/strategies/sign-up.strategy';
import { CommandBus } from '@nestjs/cqrs';

@Controller()
@UseInterceptors(ClassSerializerInterceptor)
export class AuthenticationController {
constructor(
private authService: AuthenticationService,
private readonly passwordRecovery: PasswordRecoveryService,
private readonly commandBus: CommandBus,
) {}

@Public()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export class RequestPasswordRecoveryCommand {
constructor(public readonly email: string) {}
}
32 changes: 32 additions & 0 deletions api/src/modules/auth/commands/request-password-recovery.handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { CommandHandler, EventBus, ICommandHandler } from '@nestjs/cqrs';
import { AuthMailer } from '@api/modules/auth/services/auth.mailer';
import { RequestPasswordRecoveryCommand } from '@api/modules/auth/commands/request-password-recovery.command';
import { UsersService } from '@api/modules/users/users.service';
import { PasswordRecoveryRequestedEvent } from '@api/modules/events/user-events/password-recovery-requested.event';
import { UnauthorizedException } from '@nestjs/common';

@CommandHandler(RequestPasswordRecoveryCommand)
export class RequestPasswordRecoveryHandler
implements ICommandHandler<RequestPasswordRecoveryCommand>
{
constructor(
private readonly users: UsersService,
private readonly authMailer: AuthMailer,
private readonly eventBus: EventBus,
) {}

async execute(command: RequestPasswordRecoveryCommand): Promise<void> {
const { email } = command;
const user = await this.users.findByEmail(email);
if (!user) {
this.eventBus.publish(new PasswordRecoveryRequestedEvent(email, null));
throw new UnauthorizedException();
}
await this.authMailer.sendPasswordRecoveryEmail({
user,
// TODO: Origin must come from env vars
origin,
});
this.eventBus.publish(new PasswordRecoveryRequestedEvent(email, user.id));
}
}
7 changes: 4 additions & 3 deletions api/src/modules/auth/services/auth.mailer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,13 @@ export class AuthMailer {
user: User;
defaultPassword: string;
}) {
const { emailConfirmationToken, expiresIn } =
await this.jwt.signEmailConfirmationToken(welcomeEmailDto.user.id);
const { signUpToken, expiresIn } = await this.jwt.signSignUpToken(
welcomeEmailDto.user.id,
);

// TODO: We need to know the URL to confirm the email, we could rely on origin but we would need to pass it through a lot of code.
// probably better to have a config value for this.
const resetPasswordUrl = `TODO/auth/sign-up/${emailConfirmationToken}`;
const resetPasswordUrl = `TODO/auth/sign-up/${signUpToken}`;

const htmlContent: string = `
<h1>Dear User,</h1>
Expand Down
8 changes: 4 additions & 4 deletions api/src/modules/auth/services/jwt.manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,15 @@ export class JwtManager {
};
}

async signEmailConfirmationToken(
async signSignUpToken(
userId: string,
): Promise<{ emailConfirmationToken: string; expiresIn: string }> {
const { token: emailConfirmationToken, expiresIn } = await this.sign(
): Promise<{ signUpToken: string; expiresIn: string }> {
const { token: signUpToken, expiresIn } = await this.sign(
userId,
TOKEN_TYPE_ENUM.SIGN_UP,
);
return {
emailConfirmationToken,
signUpToken: signUpToken,
expiresIn,
};
}
Expand Down
3 changes: 2 additions & 1 deletion api/src/modules/users/users.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ConflictException, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from '@shared/entities/users/user.entity';
import { Repository } from 'typeorm';
import * as bcrypt from 'bcrypt';

@Injectable()
export class UsersService {
Expand All @@ -26,7 +27,7 @@ export class UsersService {
}

async updatePassword(user: User, newPassword: string) {
user.password = newPassword;
user.password = await bcrypt.hash(newPassword, 10);
return this.repo.save(user);
}

Expand Down
2 changes: 1 addition & 1 deletion api/test/integration/auth/create-user.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ describe('Create Users', () => {
);
});

test('An Admin registers a new user', async () => {
test('An Admin registers a new user ', async () => {
// Given a admin user exists with valid credentials
// beforeAll
const newUser = {
Expand Down
23 changes: 13 additions & 10 deletions api/test/integration/auth/sign-up.spec.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
import { TestManager } from '../../utils/test-manager';
import { HttpStatus } from '@nestjs/common';
import { ApiConfigService } from '@api/modules/config/app-config.service';
import { JwtService } from '@nestjs/jwt';
import { TOKEN_TYPE_ENUM } from '@shared/schemas/auth/token-type.schema';
import { authContract } from '@shared/contracts/auth.contract';
import { ROLES } from '@api/modules/auth/roles.enum';
import { JwtManager } from '@api/modules/auth/services/jwt.manager';

//create-user.feature

describe('Create Users', () => {
let testManager: TestManager;
let apiConfig: ApiConfigService;
let jwtService: JwtService;
let jwtManager: JwtManager;

beforeAll(async () => {
testManager = await TestManager.createTestManager();
apiConfig = testManager.getModule<ApiConfigService>(ApiConfigService);
jwtService = testManager.getModule<JwtService>(JwtService);
jwtManager = testManager.getModule<JwtManager>(JwtManager);
});

afterEach(async () => {
Expand All @@ -30,17 +28,13 @@ describe('Create Users', () => {
test('A sign-up token should not be valid if the user bound to that token has already been activated', async () => {
// Given a user exists with valid credentials
// But the user has the role partner

const user = await testManager.mocks().createUser({
role: ROLES.PARTNER,
email: '[email protected]',
isActive: true,
});
const { secret, expiresIn } = apiConfig.getJWTConfigByType(
TOKEN_TYPE_ENUM.SIGN_UP,
);

const token = jwtService.sign({ id: user.id }, { secret, expiresIn });
const token = jwtManager.signSignUpToken(user.id);

// When the user creates a new user

Expand All @@ -52,4 +46,13 @@ describe('Create Users', () => {

expect(response.status).toBe(HttpStatus.UNAUTHORIZED);
});

test('Sign up should fail if the current password is incorrect', async () => {
const user = await testManager.mocks().createUser({
role: ROLES.PARTNER,
email: '[email protected]',
isActive: true,
});
const token = await jwtManager.signSignUpToken(user.id);
});
});

0 comments on commit 4eafcae

Please sign in to comment.