add opa, refactor auth to authentication

This commit is contained in:
Gsk54
2023-01-16 15:03:58 +01:00
parent 0a2a44bc15
commit 6802cd3620
61 changed files with 456 additions and 381 deletions

View File

@@ -0,0 +1,20 @@
import { AutoMap } from '@automapper/classes';
import { IsEnum, IsNotEmpty, IsString } from 'class-validator';
import { Type } from './type.enum';
export class AddUsernameRequest {
@IsString()
@IsNotEmpty()
@AutoMap()
uuid: string;
@IsString()
@IsNotEmpty()
@AutoMap()
username: string;
@IsEnum(Type)
@IsNotEmpty()
@AutoMap()
type: Type;
}

View File

@@ -0,0 +1,25 @@
import { AutoMap } from '@automapper/classes';
import { IsEnum, IsNotEmpty, IsString } from 'class-validator';
import { Type } from './type.enum';
export class CreateAuthenticationRequest {
@IsString()
@IsNotEmpty()
@AutoMap()
uuid: string;
@IsString()
@IsNotEmpty()
@AutoMap()
username: string;
@IsString()
@IsNotEmpty()
@AutoMap()
password: string;
@IsEnum(Type)
@IsNotEmpty()
@AutoMap()
type: Type;
}

View File

@@ -0,0 +1,9 @@
import { AutoMap } from '@automapper/classes';
import { IsNotEmpty, IsString } from 'class-validator';
export class DeleteAuthenticationRequest {
@IsString()
@IsNotEmpty()
@AutoMap()
uuid: string;
}

View File

@@ -0,0 +1,9 @@
import { AutoMap } from '@automapper/classes';
import { IsNotEmpty, IsString } from 'class-validator';
export class DeleteUsernameRequest {
@IsString()
@IsNotEmpty()
@AutoMap()
username: string;
}

View File

@@ -0,0 +1,4 @@
export enum Type {
EMAIL = 'EMAIL',
PHONE = 'PHONE',
}

View File

@@ -0,0 +1,14 @@
import { AutoMap } from '@automapper/classes';
import { IsNotEmpty, IsString } from 'class-validator';
export class UpdatePasswordRequest {
@IsString()
@IsNotEmpty()
@AutoMap()
uuid: string;
@IsString()
@IsNotEmpty()
@AutoMap()
password: string;
}

View File

@@ -0,0 +1,20 @@
import { AutoMap } from '@automapper/classes';
import { IsEnum, IsNotEmpty, IsString } from 'class-validator';
import { Type } from './type.enum';
export class UpdateUsernameRequest {
@IsString()
@IsNotEmpty()
@AutoMap()
uuid: string;
@IsString()
@IsNotEmpty()
@AutoMap()
username: string;
@IsEnum(Type)
@IsNotEmpty()
@AutoMap()
type: Type;
}

View File

@@ -0,0 +1,11 @@
import { IsNotEmpty, IsString } from 'class-validator';
export class ValidateAuthenticationRequest {
@IsString()
@IsNotEmpty()
username: string;
@IsString()
@IsNotEmpty()
password: string;
}

View File

@@ -0,0 +1,8 @@
import { AutoMap } from '@automapper/classes';
export class Authentication {
@AutoMap()
uuid: string;
password: string;
}

View File

@@ -0,0 +1,13 @@
import { AutoMap } from '@automapper/classes';
import { Type } from '../dtos/type.enum';
export class Username {
@AutoMap()
uuid: string;
@AutoMap()
username: string;
@AutoMap()
type: Type;
}

View File

@@ -0,0 +1,12 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export abstract class IMessageBroker {
exchange: string;
constructor(exchange: string) {
this.exchange = exchange;
}
abstract publish(routingKey: string, message: string): void;
}

View File

@@ -0,0 +1,33 @@
import { CommandHandler } from '@nestjs/cqrs';
import { LoggingMessager } from '../../adapters/secondaries/logging.messager';
import { UsernameRepository } from '../../adapters/secondaries/username.repository';
import { AddUsernameCommand } from '../../commands/add-username.command';
import { Username } from '../entities/username';
@CommandHandler(AddUsernameCommand)
export class AddUsernameUseCase {
constructor(
private readonly _usernameRepository: UsernameRepository,
private readonly _loggingMessager: LoggingMessager,
) {}
async execute(command: AddUsernameCommand): Promise<Username> {
const { uuid, username, type } = command.addUsernameRequest;
try {
return await this._usernameRepository.create({
uuid,
type,
username,
});
} catch (error) {
this._loggingMessager.publish(
'auth.username.add.warning',
JSON.stringify({
command,
error,
}),
);
throw error;
}
}
}

View File

@@ -0,0 +1,44 @@
import { CommandHandler } from '@nestjs/cqrs';
import { AuthenticationRepository } from '../../adapters/secondaries/authentication.repository';
import { CreateAuthenticationCommand } from '../../commands/create-authentication.command';
import { Authentication } from '../entities/authentication';
import * as bcrypt from 'bcrypt';
import { UsernameRepository } from '../../adapters/secondaries/username.repository';
import { LoggingMessager } from '../../adapters/secondaries/logging.messager';
@CommandHandler(CreateAuthenticationCommand)
export class CreateAuthenticationUseCase {
constructor(
private readonly _authenticationRepository: AuthenticationRepository,
private readonly _usernameRepository: UsernameRepository,
private readonly _loggingMessager: LoggingMessager,
) {}
async execute(command: CreateAuthenticationCommand): Promise<Authentication> {
const { uuid, password, ...username } = command.createAuthenticationRequest;
const hash = await bcrypt.hash(password, 10);
try {
const auth = await this._authenticationRepository.create({
uuid,
password: hash,
});
await this._usernameRepository.create({
uuid,
...username,
});
return auth;
} catch (error) {
this._loggingMessager.publish(
'auth.create.crit',
JSON.stringify({
command,
error,
}),
);
throw error;
}
}
}

View File

@@ -0,0 +1,34 @@
import { CommandHandler } from '@nestjs/cqrs';
import { AuthenticationRepository } from '../../adapters/secondaries/authentication.repository';
import { LoggingMessager } from '../../adapters/secondaries/logging.messager';
import { UsernameRepository } from '../../adapters/secondaries/username.repository';
import { DeleteAuthenticationCommand } from '../../commands/delete-authentication.command';
@CommandHandler(DeleteAuthenticationCommand)
export class DeleteAuthenticationUseCase {
constructor(
private readonly _authenticationRepository: AuthenticationRepository,
private readonly _usernameRepository: UsernameRepository,
private readonly _loggingMessager: LoggingMessager,
) {}
async execute(command: DeleteAuthenticationCommand) {
try {
await this._usernameRepository.deleteMany({
uuid: command.deleteAuthenticationRequest.uuid,
});
return await this._authenticationRepository.delete(
command.deleteAuthenticationRequest.uuid,
);
} catch (error) {
this._loggingMessager.publish(
'auth.delete.crit',
JSON.stringify({
command,
error,
}),
);
throw error;
}
}
}

View File

@@ -0,0 +1,38 @@
import { UnauthorizedException } from '@nestjs/common';
import { CommandHandler } from '@nestjs/cqrs';
import { LoggingMessager } from '../../adapters/secondaries/logging.messager';
import { UsernameRepository } from '../../adapters/secondaries/username.repository';
import { DeleteUsernameCommand } from '../../commands/delete-username.command';
@CommandHandler(DeleteUsernameCommand)
export class DeleteUsernameUseCase {
constructor(
private readonly _usernameRepository: UsernameRepository,
private readonly _loggingMessager: LoggingMessager,
) {}
async execute(command: DeleteUsernameCommand) {
try {
const { username } = command.deleteUsernameRequest;
const usernameFound = await this._usernameRepository.findOne({
username,
});
const usernames = await this._usernameRepository.findAll(1, 1, {
uuid: usernameFound.uuid,
});
if (usernames.total > 1) {
return await this._usernameRepository.deleteMany({ username });
}
throw new UnauthorizedException();
} catch (error) {
this._loggingMessager.publish(
'auth.username.delete.warning',
JSON.stringify({
command,
error,
}),
);
throw error;
}
}
}

View File

@@ -0,0 +1,34 @@
import { CommandHandler } from '@nestjs/cqrs';
import { AuthenticationRepository } from '../../adapters/secondaries/authentication.repository';
import { Authentication } from '../entities/authentication';
import * as bcrypt from 'bcrypt';
import { UpdatePasswordCommand } from '../../commands/update-password.command';
import { LoggingMessager } from '../../adapters/secondaries/logging.messager';
@CommandHandler(UpdatePasswordCommand)
export class UpdatePasswordUseCase {
constructor(
private readonly _authenticationRepository: AuthenticationRepository,
private readonly _loggingMessager: LoggingMessager,
) {}
async execute(command: UpdatePasswordCommand): Promise<Authentication> {
const { uuid, password } = command.updatePasswordRequest;
const hash = await bcrypt.hash(password, 10);
try {
return await this._authenticationRepository.update(uuid, {
password: hash,
});
} catch (error) {
this._loggingMessager.publish(
'auth.password.update.warning',
JSON.stringify({
command,
error,
}),
);
throw error;
}
}
}

View File

@@ -0,0 +1,67 @@
import { Mapper } from '@automapper/core';
import { InjectMapper } from '@automapper/nestjs';
import { BadRequestException } from '@nestjs/common';
import { CommandBus, CommandHandler } from '@nestjs/cqrs';
import { LoggingMessager } from '../../adapters/secondaries/logging.messager';
import { UsernameRepository } from '../../adapters/secondaries/username.repository';
import { AddUsernameCommand } from '../../commands/add-username.command';
import { UpdateUsernameCommand } from '../../commands/update-username.command';
import { AddUsernameRequest } from '../dtos/add-username.request';
import { UpdateUsernameRequest } from '../dtos/update-username.request';
import { Username } from '../entities/username';
@CommandHandler(UpdateUsernameCommand)
export class UpdateUsernameUseCase {
constructor(
private readonly _usernameRepository: UsernameRepository,
private readonly _commandBus: CommandBus,
@InjectMapper() private readonly _mapper: Mapper,
private readonly _loggingMessager: LoggingMessager,
) {}
async execute(command: UpdateUsernameCommand): Promise<Username> {
const { uuid, username, type } = command.updateUsernameRequest;
if (!username) throw new BadRequestException();
// update username if it exists, otherwise create it
const existingUsername = await this._usernameRepository.findOne({
uuid,
type,
});
if (existingUsername) {
try {
return await this._usernameRepository.updateWhere(
{
uuid_type: {
uuid,
type,
},
},
{
username,
},
);
} catch (error) {
this._loggingMessager.publish(
'auth.username.update.warning',
JSON.stringify({
command,
error,
}),
);
throw error;
}
}
const addUsernameRequest = this._mapper.map(
command.updateUsernameRequest,
UpdateUsernameRequest,
AddUsernameRequest,
);
try {
return await this._commandBus.execute(
new AddUsernameCommand(addUsernameRequest),
);
} catch (e) {
throw e;
}
}
}

View File

@@ -0,0 +1,41 @@
import { QueryHandler } from '@nestjs/cqrs';
import { AuthenticationRepository } from '../../adapters/secondaries/authentication.repository';
import { ValidateAuthenticationQuery as ValidateAuthenticationQuery } from '../../queries/validate-authentication.query';
import { Authentication } from '../entities/authentication';
import * as bcrypt from 'bcrypt';
import { NotFoundException, UnauthorizedException } from '@nestjs/common';
import { UsernameRepository } from '../../adapters/secondaries/username.repository';
import { Username } from '../entities/username';
@QueryHandler(ValidateAuthenticationQuery)
export class ValidateAuthenticationUseCase {
constructor(
private readonly _authenticationRepository: AuthenticationRepository,
private readonly _usernameRepository: UsernameRepository,
) {}
async execute(
validate: ValidateAuthenticationQuery,
): Promise<Authentication> {
let username = new Username();
try {
username = await this._usernameRepository.findOne({
username: validate.username,
});
} catch (e) {
throw new NotFoundException();
}
try {
const auth = await this._authenticationRepository.findOne({
uuid: username.uuid,
});
if (auth) {
const isMatch = await bcrypt.compare(validate.password, auth.password);
if (isMatch) return auth;
}
throw new UnauthorizedException();
} catch (e) {
throw new UnauthorizedException();
}
}
}