add opa, refactor auth to authentication
This commit is contained in:
parent
0a2a44bc15
commit
6802cd3620
|
@ -10,3 +10,6 @@ RMQ_URI=amqp://v3-broker:5672
|
|||
|
||||
# POSTGRES
|
||||
POSTGRES_IMAGE=postgres:15.0
|
||||
|
||||
# OPA
|
||||
OPA_IMAGE=openpolicyagent/opa:0.48.0-rootless
|
||||
|
|
|
@ -10,3 +10,6 @@ RMQ_URI=amqp://v3-broker:5672
|
|||
|
||||
# POSTGRES
|
||||
POSTGRES_IMAGE=postgres:15.0
|
||||
|
||||
# OPA
|
||||
OPA_IMAGE=openpolicyagent/opa:0.48.0-rootless
|
||||
|
|
|
@ -51,6 +51,25 @@ services:
|
|||
aliases:
|
||||
- v3-auth-db-test
|
||||
|
||||
opa:
|
||||
container_name: v3-opa
|
||||
image: ${OPA_IMAGE}
|
||||
ports:
|
||||
- 8181:8181
|
||||
command:
|
||||
- "run"
|
||||
- "--server"
|
||||
- "--log-format=json-pretty"
|
||||
- "--set=decision_logs.console=true"
|
||||
- "--set=default_decision=example/allow"
|
||||
- "./policies/"
|
||||
volumes:
|
||||
- ./opa:/policies
|
||||
networks:
|
||||
v3-network:
|
||||
aliases:
|
||||
- v3-opa
|
||||
|
||||
networks:
|
||||
v3-network:
|
||||
name: v3-network
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
package user1
|
||||
|
||||
default allow := false
|
||||
|
||||
allow := true {
|
||||
input.user == "jean"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package user2
|
||||
|
||||
default allow := false
|
||||
|
||||
allow := true {
|
||||
input.user == "pierre"
|
||||
}
|
|
@ -2,13 +2,13 @@ import { classes } from '@automapper/classes';
|
|||
import { AutomapperModule } from '@automapper/nestjs';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { AuthModule } from './modules/auth/auth.module';
|
||||
import { AuthenticationModule } from './modules/authentication/authentication.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({ isGlobal: true }),
|
||||
AutomapperModule.forRoot({ strategyInitializer: classes() }),
|
||||
AuthModule,
|
||||
AuthenticationModule,
|
||||
],
|
||||
controllers: [],
|
||||
providers: [],
|
||||
|
|
|
@ -9,10 +9,10 @@ async function bootstrap() {
|
|||
{
|
||||
transport: Transport.GRPC,
|
||||
options: {
|
||||
package: 'auth',
|
||||
package: 'authentication',
|
||||
protoPath: join(
|
||||
__dirname,
|
||||
'modules/auth/adapters/primaries/auth.proto',
|
||||
'modules/auth/adapters/primaries/authentication.proto',
|
||||
),
|
||||
url: process.env.SERVICE_URL + ':' + process.env.SERVICE_PORT,
|
||||
loader: { keepCase: true, enums: String },
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { AuthNZRepository } from '../../../database/src/domain/authnz-repository';
|
||||
import { Auth } from '../../domain/entities/auth';
|
||||
|
||||
@Injectable()
|
||||
export class AuthRepository extends AuthNZRepository<Auth> {
|
||||
protected _model = 'auth';
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
import { CreateAuthRequest } from '../domain/dtos/create-auth.request';
|
||||
|
||||
export class CreateAuthCommand {
|
||||
readonly createAuthRequest: CreateAuthRequest;
|
||||
|
||||
constructor(request: CreateAuthRequest) {
|
||||
this.createAuthRequest = request;
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
import { DeleteAuthRequest } from '../domain/dtos/delete-auth.request';
|
||||
|
||||
export class DeleteAuthCommand {
|
||||
readonly deleteAuthRequest: DeleteAuthRequest;
|
||||
|
||||
constructor(request: DeleteAuthRequest) {
|
||||
this.deleteAuthRequest = request;
|
||||
}
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
import { classes } from '@automapper/classes';
|
||||
import { AutomapperModule } from '@automapper/nestjs';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AuthRepository } from '../../adapters/secondaries/auth.repository';
|
||||
import { CreateAuthCommand } from '../../commands/create-auth.command';
|
||||
import { CreateAuthRequest } from '../../domain/dtos/create-auth.request';
|
||||
import { Auth } from '../../domain/entities/auth';
|
||||
import { CreateAuthUseCase } from '../../domain/usecases/create-auth.usecase';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import { UsernameRepository } from '../../adapters/secondaries/username.repository';
|
||||
import { Type } from '../../domain/dtos/type.enum';
|
||||
import { LoggingMessager } from '../../adapters/secondaries/logging.messager';
|
||||
|
||||
const newAuthRequest: CreateAuthRequest = new CreateAuthRequest();
|
||||
newAuthRequest.uuid = 'bb281075-1b98-4456-89d6-c643d3044a91';
|
||||
newAuthRequest.username = 'john.doe@email.com';
|
||||
newAuthRequest.password = 'John123';
|
||||
newAuthRequest.type = Type.EMAIL;
|
||||
const newAuthCommand: CreateAuthCommand = new CreateAuthCommand(newAuthRequest);
|
||||
|
||||
const mockAuthRepository = {
|
||||
create: jest
|
||||
.fn()
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve({
|
||||
uuid: newAuthRequest.uuid,
|
||||
password: bcrypt.hashSync(newAuthRequest.password, 10),
|
||||
});
|
||||
})
|
||||
.mockImplementation(() => {
|
||||
throw new Error('Already exists');
|
||||
}),
|
||||
};
|
||||
|
||||
const mockUsernameRepository = {
|
||||
create: jest.fn().mockResolvedValue({
|
||||
uuid: newAuthRequest.uuid,
|
||||
username: newAuthRequest.username,
|
||||
type: newAuthRequest.type,
|
||||
}),
|
||||
};
|
||||
|
||||
const mockMessager = {
|
||||
publish: jest.fn().mockImplementation(),
|
||||
};
|
||||
|
||||
describe('CreateAuthUseCase', () => {
|
||||
let createAuthUseCase: CreateAuthUseCase;
|
||||
|
||||
beforeAll(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [AutomapperModule.forRoot({ strategyInitializer: classes() })],
|
||||
providers: [
|
||||
{
|
||||
provide: AuthRepository,
|
||||
useValue: mockAuthRepository,
|
||||
},
|
||||
{
|
||||
provide: UsernameRepository,
|
||||
useValue: mockUsernameRepository,
|
||||
},
|
||||
{
|
||||
provide: LoggingMessager,
|
||||
useValue: mockMessager,
|
||||
},
|
||||
CreateAuthUseCase,
|
||||
],
|
||||
}).compile();
|
||||
|
||||
createAuthUseCase = module.get<CreateAuthUseCase>(CreateAuthUseCase);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(createAuthUseCase).toBeDefined();
|
||||
});
|
||||
|
||||
describe('execute', () => {
|
||||
it('should create an auth with an encrypted password', async () => {
|
||||
const newAuth: Auth = await createAuthUseCase.execute(newAuthCommand);
|
||||
|
||||
expect(
|
||||
bcrypt.compareSync(newAuthRequest.password, newAuth.password),
|
||||
).toBeTruthy();
|
||||
});
|
||||
it('should throw an error if user already exists', async () => {
|
||||
await expect(
|
||||
createAuthUseCase.execute(newAuthCommand),
|
||||
).rejects.toBeInstanceOf(Error);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,104 +0,0 @@
|
|||
import { classes } from '@automapper/classes';
|
||||
import { AutomapperModule } from '@automapper/nestjs';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AuthRepository } from '../../adapters/secondaries/auth.repository';
|
||||
import { Auth } from '../../domain/entities/auth';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import { ValidateAuthUseCase } from '../../domain/usecases/validate-auth.usecase';
|
||||
import { ValidateAuthQuery } from '../../queries/validate-auth.query';
|
||||
import { UsernameRepository } from '../../adapters/secondaries/username.repository';
|
||||
import { Type } from '../../domain/dtos/type.enum';
|
||||
import { NotFoundException, UnauthorizedException } from '@nestjs/common';
|
||||
import { DatabaseException } from '../../../database/src/exceptions/DatabaseException';
|
||||
import { ValidateAuthRequest } from '../../domain/dtos/validate-auth.request';
|
||||
|
||||
const mockAuthRepository = {
|
||||
findOne: jest
|
||||
.fn()
|
||||
.mockImplementationOnce(() => ({
|
||||
uuid: 'bb281075-1b98-4456-89d6-c643d3044a91',
|
||||
password: bcrypt.hashSync('John123', 10),
|
||||
}))
|
||||
.mockImplementationOnce(() => ({
|
||||
uuid: 'bb281075-1b98-4456-89d6-c643d3044a91',
|
||||
password: bcrypt.hashSync('John123', 10),
|
||||
})),
|
||||
};
|
||||
|
||||
const mockUsernameRepository = {
|
||||
findOne: jest
|
||||
.fn()
|
||||
.mockImplementationOnce(() => ({
|
||||
uuid: 'bb281075-1b98-4456-89d6-c643d3044a91',
|
||||
username: 'john.doe@email.com',
|
||||
type: Type.EMAIL,
|
||||
}))
|
||||
.mockImplementationOnce(() => {
|
||||
throw new DatabaseException();
|
||||
})
|
||||
.mockImplementationOnce(() => ({
|
||||
uuid: 'bb281075-1b98-4456-89d6-c643d3044a91',
|
||||
username: 'john.doe@email.com',
|
||||
type: Type.EMAIL,
|
||||
})),
|
||||
};
|
||||
|
||||
describe('ValidateAuthUseCase', () => {
|
||||
let validateAuthUseCase: ValidateAuthUseCase;
|
||||
|
||||
beforeAll(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [AutomapperModule.forRoot({ strategyInitializer: classes() })],
|
||||
providers: [
|
||||
{
|
||||
provide: AuthRepository,
|
||||
useValue: mockAuthRepository,
|
||||
},
|
||||
{
|
||||
provide: UsernameRepository,
|
||||
useValue: mockUsernameRepository,
|
||||
},
|
||||
ValidateAuthUseCase,
|
||||
],
|
||||
}).compile();
|
||||
|
||||
validateAuthUseCase = module.get<ValidateAuthUseCase>(ValidateAuthUseCase);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(validateAuthUseCase).toBeDefined();
|
||||
});
|
||||
|
||||
describe('execute', () => {
|
||||
it('should validate an auth and returns entity object', async () => {
|
||||
const validateAuthRequest: ValidateAuthRequest =
|
||||
new ValidateAuthRequest();
|
||||
validateAuthRequest.username = 'john.doe@email.com';
|
||||
validateAuthRequest.password = 'John123';
|
||||
const auth: Auth = await validateAuthUseCase.execute(
|
||||
new ValidateAuthQuery(
|
||||
validateAuthRequest.username,
|
||||
validateAuthRequest.password,
|
||||
),
|
||||
);
|
||||
|
||||
expect(auth.uuid).toBe('bb281075-1b98-4456-89d6-c643d3044a91');
|
||||
});
|
||||
|
||||
it('should not validate an auth with unknown username and returns not found exception', async () => {
|
||||
await expect(
|
||||
validateAuthUseCase.execute(
|
||||
new ValidateAuthQuery('jane.doe@email.com', 'Jane123'),
|
||||
),
|
||||
).rejects.toBeInstanceOf(NotFoundException);
|
||||
});
|
||||
|
||||
it('should not validate an auth with wrong password and returns unauthorized exception', async () => {
|
||||
await expect(
|
||||
validateAuthUseCase.execute(
|
||||
new ValidateAuthQuery('john.doe@email.com', 'John1234'),
|
||||
),
|
||||
).rejects.toBeInstanceOf(UnauthorizedException);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -4,11 +4,11 @@ import { CommandBus } from '@nestjs/cqrs';
|
|||
import { UpdateUsernameCommand } from '../../commands/update-username.command';
|
||||
import { Type } from '../../domain/dtos/type.enum';
|
||||
import { UpdateUsernameRequest } from '../../domain/dtos/update-username.request';
|
||||
import { DeleteAuthRequest } from '../../domain/dtos/delete-auth.request';
|
||||
import { DeleteAuthCommand } from '../../commands/delete-auth.command';
|
||||
import { DeleteAuthenticationRequest } from '../../domain/dtos/delete-authentication.request';
|
||||
import { DeleteAuthenticationCommand } from '../../commands/delete-authentication.command';
|
||||
|
||||
@Controller()
|
||||
export class AuthMessagerController {
|
||||
export class AuthenticationMessagerController {
|
||||
constructor(private readonly _commandBus: CommandBus) {}
|
||||
|
||||
@RabbitSubscribe({
|
||||
|
@ -47,8 +47,10 @@ export class AuthMessagerController {
|
|||
public async userDeletedHandler(message: string) {
|
||||
const deletedUser = JSON.parse(message);
|
||||
if (!deletedUser.hasOwnProperty('uuid')) throw new Error();
|
||||
const deleteAuthRequest = new DeleteAuthRequest();
|
||||
const deleteAuthRequest = new DeleteAuthenticationRequest();
|
||||
deleteAuthRequest.uuid = deletedUser.uuid;
|
||||
await this._commandBus.execute(new DeleteAuthCommand(deleteAuthRequest));
|
||||
await this._commandBus.execute(
|
||||
new DeleteAuthenticationCommand(deleteAuthRequest),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -5,22 +5,22 @@ import { CommandBus, QueryBus } from '@nestjs/cqrs';
|
|||
import { GrpcMethod, RpcException } from '@nestjs/microservices';
|
||||
import { DatabaseException } from 'src/modules/database/src/exceptions/DatabaseException';
|
||||
import { AddUsernameCommand } from '../../commands/add-username.command';
|
||||
import { CreateAuthCommand } from '../../commands/create-auth.command';
|
||||
import { DeleteAuthCommand } from '../../commands/delete-auth.command';
|
||||
import { CreateAuthenticationCommand } from '../../commands/create-authentication.command';
|
||||
import { DeleteAuthenticationCommand } from '../../commands/delete-authentication.command';
|
||||
import { DeleteUsernameCommand } from '../../commands/delete-username.command';
|
||||
import { UpdatePasswordCommand } from '../../commands/update-password.command';
|
||||
import { UpdateUsernameCommand } from '../../commands/update-username.command';
|
||||
import { AddUsernameRequest } from '../../domain/dtos/add-username.request';
|
||||
import { CreateAuthRequest } from '../../domain/dtos/create-auth.request';
|
||||
import { DeleteAuthRequest } from '../../domain/dtos/delete-auth.request';
|
||||
import { CreateAuthenticationRequest } from '../../domain/dtos/create-authentication.request';
|
||||
import { DeleteAuthenticationRequest } from '../../domain/dtos/delete-authentication.request';
|
||||
import { DeleteUsernameRequest } from '../../domain/dtos/delete-username.request';
|
||||
import { UpdatePasswordRequest } from '../../domain/dtos/update-password.request';
|
||||
import { UpdateUsernameRequest } from '../../domain/dtos/update-username.request';
|
||||
import { ValidateAuthRequest } from '../../domain/dtos/validate-auth.request';
|
||||
import { Auth } from '../../domain/entities/auth';
|
||||
import { ValidateAuthRequest } from '../../domain/dtos/validate-authentication.request';
|
||||
import { Authentication } from '../../domain/entities/authentication';
|
||||
import { Username } from '../../domain/entities/username';
|
||||
import { ValidateAuthQuery } from '../../queries/validate-auth.query';
|
||||
import { AuthPresenter } from './auth.presenter';
|
||||
import { ValidateAuthenticationQuery } from '../../queries/validate-authentication.query';
|
||||
import { AuthenticationPresenter } from './authentication.presenter';
|
||||
import { RpcValidationPipe } from './rpc.validation-pipe';
|
||||
import { UsernamePresenter } from './username.presenter';
|
||||
|
||||
|
@ -31,7 +31,7 @@ import { UsernamePresenter } from './username.presenter';
|
|||
}),
|
||||
)
|
||||
@Controller()
|
||||
export class AuthController {
|
||||
export class AuthenticationController {
|
||||
constructor(
|
||||
private readonly _commandBus: CommandBus,
|
||||
private readonly _queryBus: QueryBus,
|
||||
|
@ -39,12 +39,12 @@ export class AuthController {
|
|||
) {}
|
||||
|
||||
@GrpcMethod('AuthService', 'Validate')
|
||||
async validate(data: ValidateAuthRequest): Promise<AuthPresenter> {
|
||||
async validate(data: ValidateAuthRequest): Promise<AuthenticationPresenter> {
|
||||
try {
|
||||
const auth: Auth = await this._queryBus.execute(
|
||||
new ValidateAuthQuery(data.username, data.password),
|
||||
const auth: Authentication = await this._queryBus.execute(
|
||||
new ValidateAuthenticationQuery(data.username, data.password),
|
||||
);
|
||||
return this._mapper.map(auth, Auth, AuthPresenter);
|
||||
return this._mapper.map(auth, Authentication, AuthenticationPresenter);
|
||||
} catch (e) {
|
||||
throw new RpcException({
|
||||
code: 7,
|
||||
|
@ -54,12 +54,14 @@ export class AuthController {
|
|||
}
|
||||
|
||||
@GrpcMethod('AuthService', 'Create')
|
||||
async createUser(data: CreateAuthRequest): Promise<AuthPresenter> {
|
||||
async createUser(
|
||||
data: CreateAuthenticationRequest,
|
||||
): Promise<AuthenticationPresenter> {
|
||||
try {
|
||||
const auth: Auth = await this._commandBus.execute(
|
||||
new CreateAuthCommand(data),
|
||||
const auth: Authentication = await this._commandBus.execute(
|
||||
new CreateAuthenticationCommand(data),
|
||||
);
|
||||
return this._mapper.map(auth, Auth, AuthPresenter);
|
||||
return this._mapper.map(auth, Authentication, AuthenticationPresenter);
|
||||
} catch (e) {
|
||||
if (e instanceof DatabaseException) {
|
||||
if (e.message.includes('Already exists')) {
|
||||
|
@ -127,13 +129,15 @@ export class AuthController {
|
|||
}
|
||||
|
||||
@GrpcMethod('AuthService', 'UpdatePassword')
|
||||
async updatePassword(data: UpdatePasswordRequest): Promise<AuthPresenter> {
|
||||
async updatePassword(
|
||||
data: UpdatePasswordRequest,
|
||||
): Promise<AuthenticationPresenter> {
|
||||
try {
|
||||
const auth: Auth = await this._commandBus.execute(
|
||||
const auth: Authentication = await this._commandBus.execute(
|
||||
new UpdatePasswordCommand(data),
|
||||
);
|
||||
|
||||
return this._mapper.map(auth, Auth, AuthPresenter);
|
||||
return this._mapper.map(auth, Authentication, AuthenticationPresenter);
|
||||
} catch (e) {
|
||||
throw new RpcException({
|
||||
code: 7,
|
||||
|
@ -155,9 +159,11 @@ export class AuthController {
|
|||
}
|
||||
|
||||
@GrpcMethod('AuthService', 'Delete')
|
||||
async deleteAuth(data: DeleteAuthRequest) {
|
||||
async deleteAuth(data: DeleteAuthenticationRequest) {
|
||||
try {
|
||||
return await this._commandBus.execute(new DeleteAuthCommand(data));
|
||||
return await this._commandBus.execute(
|
||||
new DeleteAuthenticationCommand(data),
|
||||
);
|
||||
} catch (e) {
|
||||
throw new RpcException({
|
||||
code: 7,
|
|
@ -1,6 +1,6 @@
|
|||
import { AutoMap } from '@automapper/classes';
|
||||
|
||||
export class AuthPresenter {
|
||||
export class AuthenticationPresenter {
|
||||
@AutoMap()
|
||||
uuid: string;
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package auth;
|
||||
package authentication;
|
||||
|
||||
service AuthService {
|
||||
rpc Validate(AuthByUsernamePassword) returns (Uuid);
|
||||
rpc Create(Auth) returns (Uuid);
|
||||
service AuthenticationService {
|
||||
rpc Validate(AuthenticationByUsernamePassword) returns (Uuid);
|
||||
rpc Create(Authentication) returns (Uuid);
|
||||
rpc AddUsername(Username) returns (Uuid);
|
||||
rpc UpdatePassword(Password) returns (Uuid);
|
||||
rpc UpdateUsername(Username) returns (Uuid);
|
||||
|
@ -12,7 +12,7 @@ service AuthService {
|
|||
rpc Delete(Uuid) returns (Empty);
|
||||
}
|
||||
|
||||
message AuthByUsernamePassword {
|
||||
message AuthenticationByUsernamePassword {
|
||||
string username = 1;
|
||||
string password = 2;
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ enum Type {
|
|||
PHONE = 1;
|
||||
}
|
||||
|
||||
message Auth {
|
||||
message Authentication {
|
||||
string uuid = 1;
|
||||
string username = 2;
|
||||
string password = 3;
|
|
@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common';
|
|||
import { IMessageBroker } from '../../domain/interfaces/message-broker';
|
||||
|
||||
@Injectable()
|
||||
export class AuthMessager extends IMessageBroker {
|
||||
export class AuthenticationMessager extends IMessageBroker {
|
||||
constructor(private readonly _amqpConnection: AmqpConnection) {
|
||||
super('auth');
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { AuthRepository } from '../../../database/src/domain/auth-repository';
|
||||
import { Authentication } from '../../domain/entities/authentication';
|
||||
|
||||
@Injectable()
|
||||
export class AuthenticationRepository extends AuthRepository<Authentication> {
|
||||
protected _model = 'auth';
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { AuthNZRepository } from '../../../database/src/domain/authnz-repository';
|
||||
import { AuthRepository } from '../../../database/src/domain/auth-repository';
|
||||
import { Username } from '../../domain/entities/username';
|
||||
|
||||
@Injectable()
|
||||
export class UsernameRepository extends AuthNZRepository<Username> {
|
||||
export class UsernameRepository extends AuthRepository<Username> {
|
||||
protected _model = 'username';
|
||||
}
|
|
@ -1,20 +1,20 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { CqrsModule } from '@nestjs/cqrs';
|
||||
import { DatabaseModule } from '../database/database.module';
|
||||
import { AuthController } from './adapters/primaries/auth.controller';
|
||||
import { CreateAuthUseCase } from './domain/usecases/create-auth.usecase';
|
||||
import { ValidateAuthUseCase } from './domain/usecases/validate-auth.usecase';
|
||||
import { AuthProfile } from './mappers/auth.profile';
|
||||
import { AuthRepository } from './adapters/secondaries/auth.repository';
|
||||
import { AuthenticationController } from './adapters/primaries/authentication.controller';
|
||||
import { CreateAuthenticationUseCase } from './domain/usecases/create-authentication.usecase';
|
||||
import { ValidateAuthenticationUseCase } from './domain/usecases/validate-authentication.usecase';
|
||||
import { AuthenticationProfile } from './mappers/authentication.profile';
|
||||
import { AuthenticationRepository } from './adapters/secondaries/authentication.repository';
|
||||
import { UpdateUsernameUseCase } from './domain/usecases/update-username.usecase';
|
||||
import { UsernameProfile } from './mappers/username.profile';
|
||||
import { AddUsernameUseCase } from './domain/usecases/add-username.usecase';
|
||||
import { UpdatePasswordUseCase } from './domain/usecases/update-password.usecase';
|
||||
import { DeleteUsernameUseCase } from './domain/usecases/delete-username.usecase';
|
||||
import { DeleteAuthUseCase } from './domain/usecases/delete-auth.usecase';
|
||||
import { DeleteAuthenticationUseCase } from './domain/usecases/delete-authentication.usecase';
|
||||
import { RabbitMQModule } from '@golevelup/nestjs-rabbitmq';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { AuthMessagerController } from './adapters/primaries/auth-messager.controller';
|
||||
import { AuthenticationMessagerController } from './adapters/primaries/authentication-messager.controller';
|
||||
import { LoggingMessager } from './adapters/secondaries/logging.messager';
|
||||
|
||||
@Module({
|
||||
|
@ -41,20 +41,20 @@ import { LoggingMessager } from './adapters/secondaries/logging.messager';
|
|||
inject: [ConfigService],
|
||||
}),
|
||||
],
|
||||
controllers: [AuthController, AuthMessagerController],
|
||||
controllers: [AuthenticationController, AuthenticationMessagerController],
|
||||
providers: [
|
||||
AuthProfile,
|
||||
AuthenticationProfile,
|
||||
UsernameProfile,
|
||||
AuthRepository,
|
||||
AuthenticationRepository,
|
||||
LoggingMessager,
|
||||
ValidateAuthUseCase,
|
||||
CreateAuthUseCase,
|
||||
ValidateAuthenticationUseCase,
|
||||
CreateAuthenticationUseCase,
|
||||
AddUsernameUseCase,
|
||||
UpdateUsernameUseCase,
|
||||
UpdatePasswordUseCase,
|
||||
DeleteUsernameUseCase,
|
||||
DeleteAuthUseCase,
|
||||
DeleteAuthenticationUseCase,
|
||||
],
|
||||
exports: [],
|
||||
})
|
||||
export class AuthModule {}
|
||||
export class AuthenticationModule {}
|
|
@ -0,0 +1,9 @@
|
|||
import { CreateAuthenticationRequest } from '../domain/dtos/create-authentication.request';
|
||||
|
||||
export class CreateAuthenticationCommand {
|
||||
readonly createAuthenticationRequest: CreateAuthenticationRequest;
|
||||
|
||||
constructor(request: CreateAuthenticationRequest) {
|
||||
this.createAuthenticationRequest = request;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import { DeleteAuthenticationRequest } from '../domain/dtos/delete-authentication.request';
|
||||
|
||||
export class DeleteAuthenticationCommand {
|
||||
readonly deleteAuthenticationRequest: DeleteAuthenticationRequest;
|
||||
|
||||
constructor(request: DeleteAuthenticationRequest) {
|
||||
this.deleteAuthenticationRequest = request;
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ import { AutoMap } from '@automapper/classes';
|
|||
import { IsEnum, IsNotEmpty, IsString } from 'class-validator';
|
||||
import { Type } from './type.enum';
|
||||
|
||||
export class CreateAuthRequest {
|
||||
export class CreateAuthenticationRequest {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@AutoMap()
|
|
@ -1,7 +1,7 @@
|
|||
import { AutoMap } from '@automapper/classes';
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class DeleteAuthRequest {
|
||||
export class DeleteAuthenticationRequest {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@AutoMap()
|
|
@ -1,6 +1,6 @@
|
|||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class ValidateAuthRequest {
|
||||
export class ValidateAuthenticationRequest {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
username: string;
|
|
@ -1,6 +1,6 @@
|
|||
import { AutoMap } from '@automapper/classes';
|
||||
|
||||
export class Auth {
|
||||
export class Authentication {
|
||||
@AutoMap()
|
||||
uuid: string;
|
||||
|
|
@ -1,25 +1,25 @@
|
|||
import { CommandHandler } from '@nestjs/cqrs';
|
||||
import { AuthRepository } from '../../adapters/secondaries/auth.repository';
|
||||
import { CreateAuthCommand } from '../../commands/create-auth.command';
|
||||
import { Auth } from '../entities/auth';
|
||||
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(CreateAuthCommand)
|
||||
export class CreateAuthUseCase {
|
||||
@CommandHandler(CreateAuthenticationCommand)
|
||||
export class CreateAuthenticationUseCase {
|
||||
constructor(
|
||||
private readonly _authRepository: AuthRepository,
|
||||
private readonly _authenticationRepository: AuthenticationRepository,
|
||||
private readonly _usernameRepository: UsernameRepository,
|
||||
private readonly _loggingMessager: LoggingMessager,
|
||||
) {}
|
||||
|
||||
async execute(command: CreateAuthCommand): Promise<Auth> {
|
||||
const { uuid, password, ...username } = command.createAuthRequest;
|
||||
async execute(command: CreateAuthenticationCommand): Promise<Authentication> {
|
||||
const { uuid, password, ...username } = command.createAuthenticationRequest;
|
||||
const hash = await bcrypt.hash(password, 10);
|
||||
|
||||
try {
|
||||
const auth = await this._authRepository.create({
|
||||
const auth = await this._authenticationRepository.create({
|
||||
uuid,
|
||||
password: hash,
|
||||
});
|
|
@ -1,23 +1,25 @@
|
|||
import { CommandHandler } from '@nestjs/cqrs';
|
||||
import { AuthRepository } from '../../adapters/secondaries/auth.repository';
|
||||
import { AuthenticationRepository } from '../../adapters/secondaries/authentication.repository';
|
||||
import { LoggingMessager } from '../../adapters/secondaries/logging.messager';
|
||||
import { UsernameRepository } from '../../adapters/secondaries/username.repository';
|
||||
import { DeleteAuthCommand } from '../../commands/delete-auth.command';
|
||||
import { DeleteAuthenticationCommand } from '../../commands/delete-authentication.command';
|
||||
|
||||
@CommandHandler(DeleteAuthCommand)
|
||||
export class DeleteAuthUseCase {
|
||||
@CommandHandler(DeleteAuthenticationCommand)
|
||||
export class DeleteAuthenticationUseCase {
|
||||
constructor(
|
||||
private readonly _authRepository: AuthRepository,
|
||||
private readonly _authenticationRepository: AuthenticationRepository,
|
||||
private readonly _usernameRepository: UsernameRepository,
|
||||
private readonly _loggingMessager: LoggingMessager,
|
||||
) {}
|
||||
|
||||
async execute(command: DeleteAuthCommand) {
|
||||
async execute(command: DeleteAuthenticationCommand) {
|
||||
try {
|
||||
await this._usernameRepository.deleteMany({
|
||||
uuid: command.deleteAuthRequest.uuid,
|
||||
uuid: command.deleteAuthenticationRequest.uuid,
|
||||
});
|
||||
return await this._authRepository.delete(command.deleteAuthRequest.uuid);
|
||||
return await this._authenticationRepository.delete(
|
||||
command.deleteAuthenticationRequest.uuid,
|
||||
);
|
||||
} catch (error) {
|
||||
this._loggingMessager.publish(
|
||||
'auth.delete.crit',
|
|
@ -1,6 +1,6 @@
|
|||
import { CommandHandler } from '@nestjs/cqrs';
|
||||
import { AuthRepository } from '../../adapters/secondaries/auth.repository';
|
||||
import { Auth } from '../entities/auth';
|
||||
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';
|
||||
|
@ -8,16 +8,16 @@ import { LoggingMessager } from '../../adapters/secondaries/logging.messager';
|
|||
@CommandHandler(UpdatePasswordCommand)
|
||||
export class UpdatePasswordUseCase {
|
||||
constructor(
|
||||
private readonly _authRepository: AuthRepository,
|
||||
private readonly _authenticationRepository: AuthenticationRepository,
|
||||
private readonly _loggingMessager: LoggingMessager,
|
||||
) {}
|
||||
|
||||
async execute(command: UpdatePasswordCommand): Promise<Auth> {
|
||||
async execute(command: UpdatePasswordCommand): Promise<Authentication> {
|
||||
const { uuid, password } = command.updatePasswordRequest;
|
||||
const hash = await bcrypt.hash(password, 10);
|
||||
|
||||
try {
|
||||
return await this._authRepository.update(uuid, {
|
||||
return await this._authenticationRepository.update(uuid, {
|
||||
password: hash,
|
||||
});
|
||||
} catch (error) {
|
|
@ -1,20 +1,22 @@
|
|||
import { QueryHandler } from '@nestjs/cqrs';
|
||||
import { AuthRepository } from '../../adapters/secondaries/auth.repository';
|
||||
import { ValidateAuthQuery } from '../../queries/validate-auth.query';
|
||||
import { Auth } from '../entities/auth';
|
||||
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(ValidateAuthQuery)
|
||||
export class ValidateAuthUseCase {
|
||||
@QueryHandler(ValidateAuthenticationQuery)
|
||||
export class ValidateAuthenticationUseCase {
|
||||
constructor(
|
||||
private readonly _authRepository: AuthRepository,
|
||||
private readonly _authenticationRepository: AuthenticationRepository,
|
||||
private readonly _usernameRepository: UsernameRepository,
|
||||
) {}
|
||||
|
||||
async execute(validate: ValidateAuthQuery): Promise<Auth> {
|
||||
async execute(
|
||||
validate: ValidateAuthenticationQuery,
|
||||
): Promise<Authentication> {
|
||||
let username = new Username();
|
||||
try {
|
||||
username = await this._usernameRepository.findOne({
|
||||
|
@ -24,7 +26,7 @@ export class ValidateAuthUseCase {
|
|||
throw new NotFoundException();
|
||||
}
|
||||
try {
|
||||
const auth = await this._authRepository.findOne({
|
||||
const auth = await this._authenticationRepository.findOne({
|
||||
uuid: username.uuid,
|
||||
});
|
||||
if (auth) {
|
|
@ -1,18 +1,18 @@
|
|||
import { createMap, Mapper } from '@automapper/core';
|
||||
import { AutomapperProfile, InjectMapper } from '@automapper/nestjs';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { AuthPresenter } from '../adapters/primaries/auth.presenter';
|
||||
import { Auth } from '../domain/entities/auth';
|
||||
import { AuthenticationPresenter } from '../adapters/primaries/authentication.presenter';
|
||||
import { Authentication } from '../domain/entities/authentication';
|
||||
|
||||
@Injectable()
|
||||
export class AuthProfile extends AutomapperProfile {
|
||||
export class AuthenticationProfile extends AutomapperProfile {
|
||||
constructor(@InjectMapper() mapper: Mapper) {
|
||||
super(mapper);
|
||||
}
|
||||
|
||||
override get profile() {
|
||||
return (mapper: any) => {
|
||||
createMap(mapper, Auth, AuthPresenter);
|
||||
createMap(mapper, Authentication, AuthenticationPresenter);
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
export class ValidateAuthQuery {
|
||||
export class ValidateAuthenticationQuery {
|
||||
readonly username: string;
|
||||
readonly password: string;
|
||||
|
|
@ -2,14 +2,14 @@ import { TestingModule, Test } from '@nestjs/testing';
|
|||
import { DatabaseModule } from '../../../database/database.module';
|
||||
import { PrismaService } from '../../../database/src/adapters/secondaries/prisma-service';
|
||||
import { DatabaseException } from '../../../database/src/exceptions/DatabaseException';
|
||||
import { AuthRepository } from '../../adapters/secondaries/auth.repository';
|
||||
import { AuthenticationRepository } from '../../adapters/secondaries/authentication.repository';
|
||||
import { v4 } from 'uuid';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import { Auth } from '../../domain/entities/auth';
|
||||
import { Authentication } from '../../domain/entities/authentication';
|
||||
|
||||
describe('AuthRepository', () => {
|
||||
describe('AuthenticationRepository', () => {
|
||||
let prismaService: PrismaService;
|
||||
let authRepository: AuthRepository;
|
||||
let authenticationRepository: AuthenticationRepository;
|
||||
|
||||
const createAuths = async (nbToCreate = 10) => {
|
||||
for (let i = 0; i < nbToCreate; i++) {
|
||||
|
@ -25,11 +25,13 @@ describe('AuthRepository', () => {
|
|||
beforeAll(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [DatabaseModule],
|
||||
providers: [AuthRepository, PrismaService],
|
||||
providers: [AuthenticationRepository, PrismaService],
|
||||
}).compile();
|
||||
|
||||
prismaService = module.get<PrismaService>(PrismaService);
|
||||
authRepository = module.get<AuthRepository>(AuthRepository);
|
||||
authenticationRepository = module.get<AuthenticationRepository>(
|
||||
AuthenticationRepository,
|
||||
);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
|
@ -42,7 +44,7 @@ describe('AuthRepository', () => {
|
|||
|
||||
describe('findAll', () => {
|
||||
it('should return an empty data array', async () => {
|
||||
const res = await authRepository.findAll();
|
||||
const res = await authenticationRepository.findAll();
|
||||
expect(res).toEqual({
|
||||
data: [],
|
||||
total: 0,
|
||||
|
@ -51,21 +53,21 @@ describe('AuthRepository', () => {
|
|||
|
||||
it('should return a data array with 8 auths', async () => {
|
||||
await createAuths(8);
|
||||
const auths = await authRepository.findAll();
|
||||
const auths = await authenticationRepository.findAll();
|
||||
expect(auths.data.length).toBe(8);
|
||||
expect(auths.total).toBe(8);
|
||||
});
|
||||
|
||||
it('should return a data array limited to 10 auths', async () => {
|
||||
it('should return a data array limited to 10 authentications', async () => {
|
||||
await createAuths(20);
|
||||
const auths = await authRepository.findAll();
|
||||
const auths = await authenticationRepository.findAll();
|
||||
expect(auths.data.length).toBe(10);
|
||||
expect(auths.total).toBe(20);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findOneByUuid', () => {
|
||||
it('should return an auth', async () => {
|
||||
it('should return an authentication', async () => {
|
||||
const authToFind = await prismaService.auth.create({
|
||||
data: {
|
||||
uuid: v4(),
|
||||
|
@ -73,12 +75,14 @@ describe('AuthRepository', () => {
|
|||
},
|
||||
});
|
||||
|
||||
const auth = await authRepository.findOneByUuid(authToFind.uuid);
|
||||
const auth = await authenticationRepository.findOneByUuid(
|
||||
authToFind.uuid,
|
||||
);
|
||||
expect(auth.uuid).toBe(authToFind.uuid);
|
||||
});
|
||||
|
||||
it('should return null', async () => {
|
||||
const auth = await authRepository.findOneByUuid(
|
||||
const auth = await authenticationRepository.findOneByUuid(
|
||||
'544572be-11fb-4244-8235-587221fc9104',
|
||||
);
|
||||
expect(auth).toBeNull();
|
||||
|
@ -86,59 +90,64 @@ describe('AuthRepository', () => {
|
|||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create an auth', async () => {
|
||||
it('should create an authentication', async () => {
|
||||
const beforeCount = await prismaService.auth.count();
|
||||
|
||||
const authToCreate: Auth = new Auth();
|
||||
authToCreate.uuid = v4();
|
||||
authToCreate.password = bcrypt.hashSync(`password`, 10);
|
||||
const auth = await authRepository.create(authToCreate);
|
||||
const authenticationToCreate: Authentication = new Authentication();
|
||||
authenticationToCreate.uuid = v4();
|
||||
authenticationToCreate.password = bcrypt.hashSync(`password`, 10);
|
||||
const authentication = await authenticationRepository.create(
|
||||
authenticationToCreate,
|
||||
);
|
||||
|
||||
const afterCount = await prismaService.auth.count();
|
||||
|
||||
expect(afterCount - beforeCount).toBe(1);
|
||||
expect(auth.uuid).toBeDefined();
|
||||
expect(authentication.uuid).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update auth password', async () => {
|
||||
const authToUpdate = await prismaService.auth.create({
|
||||
it('should update authentication password', async () => {
|
||||
const authenticationToUpdate = await prismaService.auth.create({
|
||||
data: {
|
||||
uuid: v4(),
|
||||
password: bcrypt.hashSync(`password`, 10),
|
||||
},
|
||||
});
|
||||
|
||||
const toUpdate: Auth = new Auth();
|
||||
const toUpdate: Authentication = new Authentication();
|
||||
toUpdate.password = bcrypt.hashSync(`newPassword`, 10);
|
||||
const updatedAuth = await authRepository.update(
|
||||
authToUpdate.uuid,
|
||||
const updatedAuthentication = await authenticationRepository.update(
|
||||
authenticationToUpdate.uuid,
|
||||
toUpdate,
|
||||
);
|
||||
|
||||
expect(updatedAuth.uuid).toBe(authToUpdate.uuid);
|
||||
expect(updatedAuthentication.uuid).toBe(authenticationToUpdate.uuid);
|
||||
});
|
||||
|
||||
it('should throw DatabaseException', async () => {
|
||||
const toUpdate: Auth = new Auth();
|
||||
const toUpdate: Authentication = new Authentication();
|
||||
toUpdate.password = bcrypt.hashSync(`newPassword`, 10);
|
||||
|
||||
await expect(
|
||||
authRepository.update('544572be-11fb-4244-8235-587221fc9104', toUpdate),
|
||||
authenticationRepository.update(
|
||||
'544572be-11fb-4244-8235-587221fc9104',
|
||||
toUpdate,
|
||||
),
|
||||
).rejects.toBeInstanceOf(DatabaseException);
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should delete an auth', async () => {
|
||||
const authToRemove = await prismaService.auth.create({
|
||||
it('should delete an authentication', async () => {
|
||||
const authenticationToRemove = await prismaService.auth.create({
|
||||
data: {
|
||||
uuid: v4(),
|
||||
password: bcrypt.hashSync(`password`, 10),
|
||||
},
|
||||
});
|
||||
await authRepository.delete(authToRemove.uuid);
|
||||
await authenticationRepository.delete(authenticationToRemove.uuid);
|
||||
|
||||
const count = await prismaService.auth.count();
|
||||
expect(count).toBe(0);
|
||||
|
@ -146,7 +155,7 @@ describe('AuthRepository', () => {
|
|||
|
||||
it('should throw DatabaseException', async () => {
|
||||
await expect(
|
||||
authRepository.delete('544572be-11fb-4244-8235-587221fc9104'),
|
||||
authenticationRepository.delete('544572be-11fb-4244-8235-587221fc9104'),
|
||||
).rejects.toBeInstanceOf(DatabaseException);
|
||||
});
|
||||
});
|
|
@ -1,7 +1,7 @@
|
|||
import { classes } from '@automapper/classes';
|
||||
import { AutomapperModule } from '@automapper/nestjs';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AuthProfile } from '../../mappers/auth.profile';
|
||||
import { AuthenticationProfile } from '../../mappers/authentication.profile';
|
||||
import { UsernameRepository } from '../../adapters/secondaries/username.repository';
|
||||
import { Username } from '../../domain/entities/username';
|
||||
import { Type } from '../../domain/dtos/type.enum';
|
||||
|
@ -50,7 +50,7 @@ describe('AddUsernameUseCase', () => {
|
|||
useValue: mockMessager,
|
||||
},
|
||||
AddUsernameUseCase,
|
||||
AuthProfile,
|
||||
AuthenticationProfile,
|
||||
],
|
||||
}).compile();
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
import { classes } from '@automapper/classes';
|
||||
import { AutomapperModule } from '@automapper/nestjs';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AuthenticationRepository } from '../../adapters/secondaries/authentication.repository';
|
||||
import { CreateAuthenticationCommand } from '../../commands/create-authentication.command';
|
||||
import { CreateAuthenticationRequest } from '../../domain/dtos/create-authentication.request';
|
||||
import { Authentication } from '../../domain/entities/authentication';
|
||||
import { CreateAuthenticationUseCase } from '../../domain/usecases/create-authentication.usecase';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import { UsernameRepository } from '../../adapters/secondaries/username.repository';
|
||||
import { Type } from '../../domain/dtos/type.enum';
|
||||
import { LoggingMessager } from '../../adapters/secondaries/logging.messager';
|
||||
|
||||
const newAuthenticationRequest: CreateAuthenticationRequest =
|
||||
new CreateAuthenticationRequest();
|
||||
newAuthenticationRequest.uuid = 'bb281075-1b98-4456-89d6-c643d3044a91';
|
||||
newAuthenticationRequest.username = 'john.doe@email.com';
|
||||
newAuthenticationRequest.password = 'John123';
|
||||
newAuthenticationRequest.type = Type.EMAIL;
|
||||
const newAuthCommand: CreateAuthenticationCommand =
|
||||
new CreateAuthenticationCommand(newAuthenticationRequest);
|
||||
|
||||
const mockAuthenticationRepository = {
|
||||
create: jest
|
||||
.fn()
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve({
|
||||
uuid: newAuthenticationRequest.uuid,
|
||||
password: bcrypt.hashSync(newAuthenticationRequest.password, 10),
|
||||
});
|
||||
})
|
||||
.mockImplementation(() => {
|
||||
throw new Error('Already exists');
|
||||
}),
|
||||
};
|
||||
|
||||
const mockUsernameRepository = {
|
||||
create: jest.fn().mockResolvedValue({
|
||||
uuid: newAuthenticationRequest.uuid,
|
||||
username: newAuthenticationRequest.username,
|
||||
type: newAuthenticationRequest.type,
|
||||
}),
|
||||
};
|
||||
|
||||
const mockMessager = {
|
||||
publish: jest.fn().mockImplementation(),
|
||||
};
|
||||
|
||||
describe('CreateAuthenticationUseCase', () => {
|
||||
let createAuthenticationUseCase: CreateAuthenticationUseCase;
|
||||
|
||||
beforeAll(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [AutomapperModule.forRoot({ strategyInitializer: classes() })],
|
||||
providers: [
|
||||
{
|
||||
provide: AuthenticationRepository,
|
||||
useValue: mockAuthenticationRepository,
|
||||
},
|
||||
{
|
||||
provide: UsernameRepository,
|
||||
useValue: mockUsernameRepository,
|
||||
},
|
||||
{
|
||||
provide: LoggingMessager,
|
||||
useValue: mockMessager,
|
||||
},
|
||||
CreateAuthenticationUseCase,
|
||||
],
|
||||
}).compile();
|
||||
|
||||
createAuthenticationUseCase = module.get<CreateAuthenticationUseCase>(
|
||||
CreateAuthenticationUseCase,
|
||||
);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(createAuthenticationUseCase).toBeDefined();
|
||||
});
|
||||
|
||||
describe('execute', () => {
|
||||
it('should create an authentication with an encrypted password', async () => {
|
||||
const newAuthentication: Authentication =
|
||||
await createAuthenticationUseCase.execute(newAuthCommand);
|
||||
|
||||
expect(
|
||||
bcrypt.compareSync(
|
||||
newAuthenticationRequest.password,
|
||||
newAuthentication.password,
|
||||
),
|
||||
).toBeTruthy();
|
||||
});
|
||||
it('should throw an error if user already exists', async () => {
|
||||
await expect(
|
||||
createAuthenticationUseCase.execute(newAuthCommand),
|
||||
).rejects.toBeInstanceOf(Error);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,13 +1,13 @@
|
|||
import { classes } from '@automapper/classes';
|
||||
import { AutomapperModule } from '@automapper/nestjs';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AuthRepository } from '../../adapters/secondaries/auth.repository';
|
||||
import { AuthenticationRepository } from '../../adapters/secondaries/authentication.repository';
|
||||
import { LoggingMessager } from '../../adapters/secondaries/logging.messager';
|
||||
import { UsernameRepository } from '../../adapters/secondaries/username.repository';
|
||||
import { DeleteAuthCommand } from '../../commands/delete-auth.command';
|
||||
import { DeleteAuthRequest } from '../../domain/dtos/delete-auth.request';
|
||||
import { DeleteAuthenticationCommand } from '../../commands/delete-authentication.command';
|
||||
import { DeleteAuthenticationRequest } from '../../domain/dtos/delete-authentication.request';
|
||||
import { Type } from '../../domain/dtos/type.enum';
|
||||
import { DeleteAuthUseCase } from '../../domain/usecases/delete-auth.usecase';
|
||||
import { DeleteAuthenticationUseCase } from '../../domain/usecases/delete-authentication.usecase';
|
||||
|
||||
const usernames = {
|
||||
data: [
|
||||
|
@ -25,13 +25,13 @@ const usernames = {
|
|||
total: 2,
|
||||
};
|
||||
|
||||
const deleteAuthRequest: DeleteAuthRequest = new DeleteAuthRequest();
|
||||
deleteAuthRequest.uuid = 'bb281075-1b98-4456-89d6-c643d3044a91';
|
||||
const deleteAuthCommand: DeleteAuthCommand = new DeleteAuthCommand(
|
||||
deleteAuthRequest,
|
||||
);
|
||||
const deleteAuthenticationRequest: DeleteAuthenticationRequest =
|
||||
new DeleteAuthenticationRequest();
|
||||
deleteAuthenticationRequest.uuid = 'bb281075-1b98-4456-89d6-c643d3044a91';
|
||||
const deleteAuthenticationCommand: DeleteAuthenticationCommand =
|
||||
new DeleteAuthenticationCommand(deleteAuthenticationRequest);
|
||||
|
||||
const mockAuthRepository = {
|
||||
const mockAuthenticationRepository = {
|
||||
delete: jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce(undefined)
|
||||
|
@ -53,16 +53,16 @@ const mockMessager = {
|
|||
publish: jest.fn().mockImplementation(),
|
||||
};
|
||||
|
||||
describe('DeleteAuthUseCase', () => {
|
||||
let deleteAuthUseCase: DeleteAuthUseCase;
|
||||
describe('DeleteAuthenticationUseCase', () => {
|
||||
let deleteAuthenticationUseCase: DeleteAuthenticationUseCase;
|
||||
|
||||
beforeAll(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [AutomapperModule.forRoot({ strategyInitializer: classes() })],
|
||||
providers: [
|
||||
{
|
||||
provide: AuthRepository,
|
||||
useValue: mockAuthRepository,
|
||||
provide: AuthenticationRepository,
|
||||
useValue: mockAuthenticationRepository,
|
||||
},
|
||||
{
|
||||
provide: UsernameRepository,
|
||||
|
@ -72,26 +72,30 @@ describe('DeleteAuthUseCase', () => {
|
|||
provide: LoggingMessager,
|
||||
useValue: mockMessager,
|
||||
},
|
||||
DeleteAuthUseCase,
|
||||
DeleteAuthenticationUseCase,
|
||||
],
|
||||
}).compile();
|
||||
|
||||
deleteAuthUseCase = module.get<DeleteAuthUseCase>(DeleteAuthUseCase);
|
||||
deleteAuthenticationUseCase = module.get<DeleteAuthenticationUseCase>(
|
||||
DeleteAuthenticationUseCase,
|
||||
);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(deleteAuthUseCase).toBeDefined();
|
||||
expect(deleteAuthenticationUseCase).toBeDefined();
|
||||
});
|
||||
|
||||
describe('execute', () => {
|
||||
it('should delete an auth and its usernames', async () => {
|
||||
const deletedAuth = await deleteAuthUseCase.execute(deleteAuthCommand);
|
||||
it('should delete an authentication and its usernames', async () => {
|
||||
const deletedAuthentication = await deleteAuthenticationUseCase.execute(
|
||||
deleteAuthenticationCommand,
|
||||
);
|
||||
|
||||
expect(deletedAuth).toBe(undefined);
|
||||
expect(deletedAuthentication).toBe(undefined);
|
||||
});
|
||||
it('should throw an error if auth does not exist', async () => {
|
||||
it('should throw an error if authentication does not exist', async () => {
|
||||
await expect(
|
||||
deleteAuthUseCase.execute(deleteAuthCommand),
|
||||
deleteAuthenticationUseCase.execute(deleteAuthenticationCommand),
|
||||
).rejects.toBeInstanceOf(Error);
|
||||
});
|
||||
});
|
|
@ -1,8 +1,8 @@
|
|||
import { classes } from '@automapper/classes';
|
||||
import { AutomapperModule } from '@automapper/nestjs';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AuthRepository } from '../../adapters/secondaries/auth.repository';
|
||||
import { Auth } from '../../domain/entities/auth';
|
||||
import { AuthenticationRepository } from '../../adapters/secondaries/authentication.repository';
|
||||
import { Authentication } from '../../domain/entities/authentication';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import { UpdatePasswordRequest } from '../../domain/dtos/update-password.request';
|
||||
import { UpdatePasswordCommand } from '../../commands/update-password.command';
|
||||
|
@ -18,7 +18,7 @@ const updatePasswordCommand: UpdatePasswordCommand = new UpdatePasswordCommand(
|
|||
updatePasswordRequest,
|
||||
);
|
||||
|
||||
const mockAuthRepository = {
|
||||
const mockAuthenticationRepository = {
|
||||
update: jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce({
|
||||
|
@ -42,8 +42,8 @@ describe('UpdatePasswordUseCase', () => {
|
|||
imports: [AutomapperModule.forRoot({ strategyInitializer: classes() })],
|
||||
providers: [
|
||||
{
|
||||
provide: AuthRepository,
|
||||
useValue: mockAuthRepository,
|
||||
provide: AuthenticationRepository,
|
||||
useValue: mockAuthenticationRepository,
|
||||
},
|
||||
{
|
||||
provide: LoggingMessager,
|
||||
|
@ -64,7 +64,7 @@ describe('UpdatePasswordUseCase', () => {
|
|||
|
||||
describe('execute', () => {
|
||||
it('should update an auth with an new encrypted password', async () => {
|
||||
const newAuth: Auth = await updatePasswordUseCase.execute(
|
||||
const newAuth: Authentication = await updatePasswordUseCase.execute(
|
||||
updatePasswordCommand,
|
||||
);
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
import { classes } from '@automapper/classes';
|
||||
import { AutomapperModule } from '@automapper/nestjs';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AuthenticationRepository } from '../../adapters/secondaries/authentication.repository';
|
||||
import { Authentication } from '../../domain/entities/authentication';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import { ValidateAuthenticationUseCase } from '../../domain/usecases/validate-authentication.usecase';
|
||||
import { ValidateAuthenticationQuery } from '../../queries/validate-authentication.query';
|
||||
import { UsernameRepository } from '../../adapters/secondaries/username.repository';
|
||||
import { Type } from '../../domain/dtos/type.enum';
|
||||
import { NotFoundException, UnauthorizedException } from '@nestjs/common';
|
||||
import { DatabaseException } from '../../../database/src/exceptions/DatabaseException';
|
||||
import { ValidateAuthenticationRequest } from '../../domain/dtos/validate-authentication.request';
|
||||
|
||||
const mockAuthenticationRepository = {
|
||||
findOne: jest
|
||||
.fn()
|
||||
.mockImplementationOnce(() => ({
|
||||
uuid: 'bb281075-1b98-4456-89d6-c643d3044a91',
|
||||
password: bcrypt.hashSync('John123', 10),
|
||||
}))
|
||||
.mockImplementationOnce(() => ({
|
||||
uuid: 'bb281075-1b98-4456-89d6-c643d3044a91',
|
||||
password: bcrypt.hashSync('John123', 10),
|
||||
})),
|
||||
};
|
||||
|
||||
const mockUsernameRepository = {
|
||||
findOne: jest
|
||||
.fn()
|
||||
.mockImplementationOnce(() => ({
|
||||
uuid: 'bb281075-1b98-4456-89d6-c643d3044a91',
|
||||
username: 'john.doe@email.com',
|
||||
type: Type.EMAIL,
|
||||
}))
|
||||
.mockImplementationOnce(() => {
|
||||
throw new DatabaseException();
|
||||
})
|
||||
.mockImplementationOnce(() => ({
|
||||
uuid: 'bb281075-1b98-4456-89d6-c643d3044a91',
|
||||
username: 'john.doe@email.com',
|
||||
type: Type.EMAIL,
|
||||
})),
|
||||
};
|
||||
|
||||
describe('ValidateAuthenticationUseCase', () => {
|
||||
let validateAuthenticationUseCase: ValidateAuthenticationUseCase;
|
||||
|
||||
beforeAll(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [AutomapperModule.forRoot({ strategyInitializer: classes() })],
|
||||
providers: [
|
||||
{
|
||||
provide: AuthenticationRepository,
|
||||
useValue: mockAuthenticationRepository,
|
||||
},
|
||||
{
|
||||
provide: UsernameRepository,
|
||||
useValue: mockUsernameRepository,
|
||||
},
|
||||
ValidateAuthenticationUseCase,
|
||||
],
|
||||
}).compile();
|
||||
|
||||
validateAuthenticationUseCase = module.get<ValidateAuthenticationUseCase>(
|
||||
ValidateAuthenticationUseCase,
|
||||
);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(validateAuthenticationUseCase).toBeDefined();
|
||||
});
|
||||
|
||||
describe('execute', () => {
|
||||
it('should validate an authentication and returns entity object', async () => {
|
||||
const validateAuthenticationRequest: ValidateAuthenticationRequest =
|
||||
new ValidateAuthenticationRequest();
|
||||
validateAuthenticationRequest.username = 'john.doe@email.com';
|
||||
validateAuthenticationRequest.password = 'John123';
|
||||
const authentication: Authentication =
|
||||
await validateAuthenticationUseCase.execute(
|
||||
new ValidateAuthenticationQuery(
|
||||
validateAuthenticationRequest.username,
|
||||
validateAuthenticationRequest.password,
|
||||
),
|
||||
);
|
||||
|
||||
expect(authentication.uuid).toBe('bb281075-1b98-4456-89d6-c643d3044a91');
|
||||
});
|
||||
|
||||
it('should not validate an authentication with unknown username and returns not found exception', async () => {
|
||||
await expect(
|
||||
validateAuthenticationUseCase.execute(
|
||||
new ValidateAuthenticationQuery('jane.doe@email.com', 'Jane123'),
|
||||
),
|
||||
).rejects.toBeInstanceOf(NotFoundException);
|
||||
});
|
||||
|
||||
it('should not validate an authentication with wrong password and returns unauthorized exception', async () => {
|
||||
await expect(
|
||||
validateAuthenticationUseCase.execute(
|
||||
new ValidateAuthenticationQuery('john.doe@email.com', 'John1234'),
|
||||
),
|
||||
).rejects.toBeInstanceOf(UnauthorizedException);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,10 +1,10 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { AuthRepository } from '../auth/adapters/secondaries/auth.repository';
|
||||
import { UsernameRepository } from '../auth/adapters/secondaries/username.repository';
|
||||
import { AuthenticationRepository } from '../authentication/adapters/secondaries/authentication.repository';
|
||||
import { UsernameRepository } from '../authentication/adapters/secondaries/username.repository';
|
||||
import { PrismaService } from './src/adapters/secondaries/prisma-service';
|
||||
|
||||
@Module({
|
||||
providers: [PrismaService, AuthRepository, UsernameRepository],
|
||||
exports: [PrismaService, AuthRepository, UsernameRepository],
|
||||
providers: [PrismaService, AuthenticationRepository, UsernameRepository],
|
||||
exports: [PrismaService, AuthenticationRepository, UsernameRepository],
|
||||
})
|
||||
export class DatabaseModule {}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import { PrismaRepository } from '../adapters/secondaries/prisma-repository.abstract';
|
||||
|
||||
export class AuthNZRepository<T> extends PrismaRepository<T> {}
|
||||
export class AuthRepository<T> extends PrismaRepository<T> {}
|
Loading…
Reference in New Issue