mirror of
https://gitlab.com/mobicoop/v3/service/auth.git
synced 2026-01-11 07:12:40 +00:00
add opa, refactor auth to authentication
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { AutoMap } from '@automapper/classes';
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class DeleteAuthenticationRequest {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@AutoMap()
|
||||
uuid: string;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { AutoMap } from '@automapper/classes';
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class DeleteUsernameRequest {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@AutoMap()
|
||||
username: string;
|
||||
}
|
||||
4
src/modules/authentication/domain/dtos/type.enum.ts
Normal file
4
src/modules/authentication/domain/dtos/type.enum.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export enum Type {
|
||||
EMAIL = 'EMAIL',
|
||||
PHONE = 'PHONE',
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class ValidateAuthenticationRequest {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
username: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
password: string;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { AutoMap } from '@automapper/classes';
|
||||
|
||||
export class Authentication {
|
||||
@AutoMap()
|
||||
uuid: string;
|
||||
|
||||
password: string;
|
||||
}
|
||||
13
src/modules/authentication/domain/entities/username.ts
Normal file
13
src/modules/authentication/domain/entities/username.ts
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user