Compare commits

..

No commits in common. "main" and "v0.6.4" have entirely different histories.
main ... v0.6.4

35 changed files with 1952 additions and 3565 deletions

View File

@ -7,15 +7,10 @@ HEALTH_SERVICE_PORT=6002
DATABASE_URL="postgresql://mobicoop:mobicoop@v3-db:5432/mobicoop?schema=auth" DATABASE_URL="postgresql://mobicoop:mobicoop@v3-db:5432/mobicoop?schema=auth"
# MESSAGE BROKER # MESSAGE BROKER
MESSAGE_BROKER_URI=amqp://mobicoop:mobicoop@v3-broker:5672 MESSAGE_BROKER_URI=amqp://v3-broker:5672
MESSAGE_BROKER_EXCHANGE=mobicoop MESSAGE_BROKER_EXCHANGE=mobicoop
MESSAGE_BROKER_EXCHANGE_DURABILITY=true MESSAGE_BROKER_EXCHANGE_DURABILITY=true
# REDIS
REDIS_HOST=v3-redis
REDIS_PASSWORD=redis
REDIS_PORT=6379
# OPA # OPA
OPA_IMAGE=docker.io/openpolicyagent/opa:0.58.0 OPA_IMAGE=openpolicyagent/opa:0.57.0
OPA_URL=http://v3-auth-opa:8181/v1/data/ OPA_URL=http://v3-auth-opa:8181/v1/data/

View File

@ -6,9 +6,9 @@ SERVICE_PORT=5002
DATABASE_URL="postgresql://mobicoop:mobicoop@localhost:5432/mobicoop-test?schema=auth" DATABASE_URL="postgresql://mobicoop:mobicoop@localhost:5432/mobicoop-test?schema=auth"
# MESSAGE BROKER # MESSAGE BROKER
MESSAGE_BROKER_URI=amqp://mobicoop:mobicoop@v3-broker:5672 MESSAGE_BROKER_URI=amqp://v3-broker:5672
MESSAGE_BROKER_EXCHANGE=mobicoop MESSAGE_BROKER_EXCHANGE=mobicoop
# OPA # OPA
OPA_IMAGE=docker.io/openpolicyagent/opa:0.54.0 OPA_IMAGE=openpolicyagent/opa:0.54.0
OPA_URL=http://v3-auth-opa:8181/v1/data/ OPA_URL=http://v3-auth-opa:8181/v1/data/

View File

@ -4,10 +4,6 @@ stages:
- test - test
- build - build
include:
- template: Security/SAST.gitlab-ci.yml
- template: Security/Secret-Detection.gitlab-ci.yml
############## ##############
# TEST STAGE # # TEST STAGE #
############## ##############

View File

@ -1,55 +0,0 @@
_Replace italic text by your own description_
## Feature Merge Request
### Why this Merge Request
_This merge request addresses, and describe the problem or user story being addressed._
### What is implemented, what is the chosen solution
_Explain the fix or solution implemented. Which other solution have been envisaged._
### Related issues and impact on other project in codebase
_Provide links to the related issues, feature requests and merge request (from Gitlab and Redmine)._
_And Link to other project Impacted._
### Other Information
_Include any extra information or considerations for reviewers._
## Checklists
### Merge Request
- [ ] Target branch identified.
- [ ] Code based on last version of target branch.
- [ ] Description filled.
- [ ] Impact on other project codebase identified.
- [ ] Documentation reflects the changes made.
- [ ] Test run in gitlab pipeline and locally.
- [ ] One or more reviewer is defined
### Code Review
- [ ] Code follows project coding guidelines.
- [ ] Code follows project designed architecture.
- [ ] Code is easily readable.
- [ ] Everything new have an explicit and pertinent name (variable, method, file ...)
- [ ] No redundant/duplicate code (unless explain by architecture choice)
- [ ] Commit are all related to MR and well written (Atomic commit).
- [ ] New code is tested and covered by automated test.
- [ ] No useless logging or debugging code.
- [ ] No code can be replaced by library or framework code.
### TODO before merge
- [ ] _add any task here_
- [ ] ...
### TODO after merge
- [ ] _add any task here_
- [ ] ...

View File

@ -1,62 +0,0 @@
_Replace italic text by your own description_
## Release Merge Request
### Why this Merge Request
_This merge request addresses, and describe the problem or user story being addressed._
### What is implemented, what is the chosen solution
_Explain the fix or solution implemented. Which other solution have been envisaged._
### Related issues and impact on other project in codebase
_Provide links to the related issues, feature requests and merge request (from Gitlab and Redmine)._
_And Link to other project Impacted._
### Other Information
_Include any extra information or considerations for reviewers._
## Checklists
### Merge Request
- [ ] Target branch identified.
- [ ] Code based on last version of target branch.
- [ ] Description filled.
- [ ] Impact on other project codebase identified.
- [ ] Documentation reflects the changes made.
- [ ] Test run in gitlab pipeline and locally.
- [ ] One or more reviewer is defined
### Code Review
- [ ] Code follows project coding guidelines.
- [ ] Code follows project designed architecture.
- [ ] Code is easily readable.
- [ ] Everything new have an explicit and pertinent name (variable, method, file ...)
- [ ] No redundant/duplicate code (unless explain by architecture choice)
- [ ] Commit are all related to MR and well written (Atomic commit).
- [ ] New code is tested and covered by automated test.
- [ ] No useless logging or debugging code.
- [ ] No code can be replaced by library or framework code.
### Change Management
- [ ] Release is planned
- [ ] Merge Request to be included are identified
- [ ] Concerned Team are aware of the change
- [ ] No other change on the same day (if possible)
### TODO before merge
- [ ] _add any task here_
- [ ] ...
### TODO after merge
- [ ] _add any task here_
- [ ] ...

View File

@ -1,37 +0,0 @@
_Replace italic text by your own description_
## Small Fix Merge Request
### Why this Merge Request
_This merge request addresses, and describe the problem or user story being addressed._
### What is implemented, what is the chosen solution
_Explain the fix or solution implemented. Which other solution have been envisaged._
### Related issues and impact on other project in codebase
_Provide links to the related issues, feature requests and merge request (from Gitlab and Redmine)._
_And Link to other project Impacted._
### Other Information
_Include any extra information or considerations for reviewers._
## Checklists
### Merge Request
- [ ] Target branch identified.
- [ ] Code based on last version of target branch.
- [ ] Description filled.
- [ ] Impact on other project codebase identified.
- [ ] Test run in gitlab pipeline and locally.
### Code Review
- [ ] Code is easily readable.
- [ ] Commit are all related to MR and well written (Atomic commit).
- [ ] No useless logging or debugging code.

View File

@ -1,3 +0,0 @@
{
"reject": ["bcrypt"]
}

View File

@ -4,4 +4,3 @@ node_modules
dist dist
coverage coverage
.prettierrc.json .prettierrc.json
.gitlab

View File

@ -1,10 +1,8 @@
ARG NODE_VERSION=20.9.0
################### ###################
# BUILD FOR LOCAL DEVELOPMENT # BUILD FOR LOCAL DEVELOPMENT
################### ###################
FROM docker.io/node:${NODE_VERSION} As development FROM node:18-alpine3.16 As development
# Create app directory # Create app directory
WORKDIR /usr/src/app WORKDIR /usr/src/app
@ -31,7 +29,7 @@ USER node
# BUILD FOR PRODUCTION # BUILD FOR PRODUCTION
################### ###################
FROM docker.io/node:${NODE_VERSION} As build FROM node:18-alpine3.16 As build
WORKDIR /usr/src/app WORKDIR /usr/src/app
@ -68,7 +66,7 @@ USER node
# PRODUCTION # PRODUCTION
################### ###################
FROM docker.io/node:${NODE_VERSION} As production FROM node:18-alpine3.16 As production
# Copy package.json to be able to execute migration command # Copy package.json to be able to execute migration command
COPY --chown=node:node package*.json ./ COPY --chown=node:node package*.json ./

View File

@ -5,8 +5,11 @@ SERVICE_PORT=5002
# PRISMA # PRISMA
DATABASE_URL="postgresql://mobicoop:mobicoop@v3-db:5432/mobicoop?schema=public" DATABASE_URL="postgresql://mobicoop:mobicoop@v3-db:5432/mobicoop?schema=public"
# RABBIT MQ
RMQ_URI=amqp://v3-auth-broker:5672
# MESSAGE BROKER # MESSAGE BROKER
MESSAGE_BROKER_IMAGE=docker.io/rabbitmq:3-alpine BROKER_IMAGE=rabbitmq:3-alpine
# POSTGRES # POSTGRES
POSTGRES_IMAGE=docker.io/postgres:15.0 POSTGRES_IMAGE=postgres:15.0

View File

@ -1,10 +1,8 @@
ARG NODE_VERSION=20.9.0
################### ###################
# BUILD FOR CI TESTING # BUILD FOR CI TESTING
################### ###################
FROM docker.io/node:${NODE_VERSION} FROM node:18-alpine3.16
# Create app directory # Create app directory
WORKDIR /usr/src/app WORKDIR /usr/src/app

View File

@ -15,7 +15,7 @@ services:
broker: broker:
container_name: v3-broker container_name: v3-broker
image: ${MESSAGE_BROKER_IMAGE} image: ${BROKER_IMAGE}
ports: ports:
- 5672:5672 - 5672:5672
networks: networks:

4815
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "@mobicoop/auth", "name": "@mobicoop/auth",
"version": "0.8.2", "version": "0.6.4",
"description": "Mobicoop V3 Auth Service", "description": "Mobicoop V3 Auth Service",
"author": "sbriat", "author": "sbriat",
"private": true, "private": true,
@ -24,65 +24,62 @@
"test:integration:ci": "npm run migrate:test:ci && dotenv -e ci/.env.ci -- jest --testPathPattern 'tests/integration/' --runInBand", "test:integration:ci": "npm run migrate:test:ci && dotenv -e ci/.env.ci -- jest --testPathPattern 'tests/integration/' --runInBand",
"test:cov": "jest --testPathPattern 'tests/unit/' --coverage", "test:cov": "jest --testPathPattern 'tests/unit/' --coverage",
"test:e2e": "jest --config ./test/jest-e2e.json", "test:e2e": "jest --config ./test/jest-e2e.json",
"repl": "docker exec -it v3-auth-api npm run start -- --entryFile repl", "migrate": "docker exec v3-auth-api sh -c 'npx prisma migrate dev'",
"migrate": "docker exec v3-auth-api sh -c 'npx prisma migrate deploy'",
"migrate:dev": "docker exec v3-auth-api sh -c 'npx prisma migrate dev'",
"migrate:test": "dotenv -e .env.test -- npx prisma migrate deploy", "migrate:test": "dotenv -e .env.test -- npx prisma migrate deploy",
"migrate:test:ci": "dotenv -e ci/.env.ci -- npx prisma migrate deploy", "migrate:test:ci": "dotenv -e ci/.env.ci -- npx prisma migrate deploy",
"migrate:deploy": "npx prisma migrate deploy" "migrate:deploy": "npx prisma migrate deploy"
}, },
"dependencies": { "dependencies": {
"@golevelup/nestjs-rabbitmq": "^4.1.0", "@golevelup/nestjs-rabbitmq": "^4.0.0",
"@grpc/grpc-js": "^1.9.13", "@grpc/grpc-js": "^1.9.9",
"@grpc/proto-loader": "^0.7.10", "@grpc/proto-loader": "^0.7.10",
"@mobicoop/configuration-module": "^8.0.0", "@mobicoop/ddd-library": "^2.1.1",
"@mobicoop/ddd-library": "^2.4.3", "@mobicoop/health-module": "^2.3.1",
"@mobicoop/health-module": "^2.3.2", "@mobicoop/message-broker-module": "^2.1.1",
"@mobicoop/message-broker-module": "^2.1.2",
"@nestjs/axios": "^3.0.1", "@nestjs/axios": "^3.0.1",
"@nestjs/common": "^10.3.0", "@nestjs/common": "^10.2.7",
"@nestjs/config": "^3.1.1", "@nestjs/config": "^3.1.1",
"@nestjs/core": "^10.3.0", "@nestjs/core": "^10.2.7",
"@nestjs/cqrs": "^10.2.6", "@nestjs/cqrs": "^10.2.6",
"@nestjs/event-emitter": "^2.0.3", "@nestjs/event-emitter": "^2.0.2",
"@nestjs/microservices": "^10.3.0", "@nestjs/microservices": "^10.2.7",
"@nestjs/platform-express": "^10.3.0", "@nestjs/platform-express": "^10.2.7",
"@nestjs/terminus": "^10.2.0", "@nestjs/terminus": "^10.1.1",
"@prisma/client": "^5.8.1", "@prisma/client": "^5.5.2",
"argon2": "^0.31.2", "axios": "^1.6.0",
"axios": "^1.6.5", "bcrypt": "5.1.1",
"bcrypt": "5.1.0",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.1", "class-validator": "^0.14.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^5.0.5", "rimraf": "^5.0.5",
"rxjs": "^7.8.1" "rxjs": "^7.8.1"
}, },
"devDependencies": { "devDependencies": {
"@nestjs/cli": "^10.3.0", "@nestjs/cli": "^10.2.1",
"@nestjs/schematics": "^10.1.0", "@nestjs/schematics": "^10.0.3",
"@nestjs/testing": "^10.3.0", "@nestjs/testing": "^10.2.7",
"@types/bcrypt": "^5.0.2", "@types/bcrypt": "^5.0.1",
"@types/express": "^4.17.21", "@types/express": "^4.17.20",
"@types/jest": "29.5.11", "@types/jest": "29.5.7",
"@types/node": "^20.11.4", "@types/node": "^20.8.10",
"@types/supertest": "^6.0.2", "@types/supertest": "^2.0.15",
"@types/uuid": "^9.0.7", "@types/uuid": "^9.0.6",
"@typescript-eslint/eslint-plugin": "^6.19.0", "@typescript-eslint/eslint-plugin": "^6.9.1",
"@typescript-eslint/parser": "^6.19.0", "@typescript-eslint/parser": "^6.9.1",
"dotenv-cli": "^7.3.0", "dotenv-cli": "^7.3.0",
"eslint": "^8.56.0", "eslint": "^8.52.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.1.3", "eslint-plugin-prettier": "^5.0.1",
"jest": "29.7.0", "jest": "29.7.0",
"prettier": "^3.2.2", "prettier": "^3.0.3",
"prisma": "^5.8.1", "prisma": "^5.5.2",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",
"supertest": "^6.3.4", "supertest": "^6.3.3",
"ts-jest": "29.1.1", "ts-jest": "29.1.1",
"ts-loader": "^9.5.1", "ts-loader": "^9.5.0",
"ts-node": "^10.9.2", "ts-node": "^10.9.1",
"tsconfig-paths": "4.2.0", "tsconfig-paths": "4.2.0",
"typescript": "^5.3.3", "typescript": "^5.2.2",
"uuid": "^9.0.1" "uuid": "^9.0.1"
}, },
"jest": { "jest": {

View File

@ -3,7 +3,7 @@
generator client { generator client {
provider = "prisma-client-js" provider = "prisma-client-js"
binaryTargets = ["linux-musl", "debian-openssl-3.0.x", "linux-musl-openssl-3.0.x"] binaryTargets = ["linux-musl", "debian-openssl-3.0.x"]
} }
datasource db { datasource db {

View File

@ -1,5 +1,5 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config'; import { ConfigModule } from '@nestjs/config';
import { AuthenticationModule } from '@modules/authentication/authentication.module'; import { AuthenticationModule } from '@modules/authentication/authentication.module';
import { EventEmitterModule } from '@nestjs/event-emitter'; import { EventEmitterModule } from '@nestjs/event-emitter';
import { import {
@ -21,10 +21,6 @@ import {
HEALTH_USERNAME_REPOSITORY, HEALTH_USERNAME_REPOSITORY,
SERVICE_NAME, SERVICE_NAME,
} from './app.constants'; } from './app.constants';
import {
ConfigurationModule,
ConfigurationModuleOptions,
} from '@mobicoop/configuration-module';
@Module({ @Module({
imports: [ imports: [
@ -57,19 +53,6 @@ import {
messagePublisher, messagePublisher,
}), }),
}), }),
ConfigurationModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (
configService: ConfigService,
): Promise<ConfigurationModuleOptions> => {
return {
host: configService.get<string>('REDIS_HOST') as string,
port: configService.get<number>('REDIS_PORT') as number,
password: configService.get<string>('REDIS_PASSWORD'),
};
},
}),
AuthenticationModule, AuthenticationModule,
AuthorizationModule, AuthorizationModule,
MessagerModule, MessagerModule,

View File

@ -1,4 +1,3 @@
import { KeyType, Type } from '@mobicoop/configuration-module';
import { IsStrongPasswordOptions } from 'class-validator'; import { IsStrongPasswordOptions } from 'class-validator';
export const STRONG_PASSWORD_OPTIONS: IsStrongPasswordOptions = { export const STRONG_PASSWORD_OPTIONS: IsStrongPasswordOptions = {
@ -8,11 +7,3 @@ export const STRONG_PASSWORD_OPTIONS: IsStrongPasswordOptions = {
minSymbols: 1, minSymbols: 1,
minUppercase: 1, minUppercase: 1,
}; };
export const AUTH_CONFIG_ENCRYPTION_ALGORITHM = 'encryptionAlgorithm';
export const AuthKeyTypes: KeyType[] = [
{
key: AUTH_CONFIG_ENCRYPTION_ALGORITHM,
type: Type.STRING,
},
];

View File

@ -1,7 +1,3 @@
export const AUTH_MESSAGE_PUBLISHER = Symbol('AUTH_MESSAGE_PUBLISHER'); export const AUTH_MESSAGE_PUBLISHER = Symbol('AUTH_MESSAGE_PUBLISHER');
export const AUTHENTICATION_REPOSITORY = Symbol('AUTHENTICATION_REPOSITORY'); export const AUTHENTICATION_REPOSITORY = Symbol('AUTHENTICATION_REPOSITORY');
export const USERNAME_REPOSITORY = Symbol('USERNAME_REPOSITORY'); export const USERNAME_REPOSITORY = Symbol('USERNAME_REPOSITORY');
export const AUTHENTICATION_CONFIGURATION_REPOSITORY = Symbol(
'AUTHENTICATION_CONFIGURATION_REPOSITORY',
);
export const PASSWORD_VERIFIER = Symbol('PASSWORD_VERIFIER');

View File

@ -4,9 +4,7 @@ import { CreateAuthenticationService } from './core/application/commands/create-
import { AuthenticationMapper } from './authentication.mapper'; import { AuthenticationMapper } from './authentication.mapper';
import { import {
AUTH_MESSAGE_PUBLISHER, AUTH_MESSAGE_PUBLISHER,
AUTHENTICATION_CONFIGURATION_REPOSITORY,
AUTHENTICATION_REPOSITORY, AUTHENTICATION_REPOSITORY,
PASSWORD_VERIFIER,
USERNAME_REPOSITORY, USERNAME_REPOSITORY,
} from './authentication.di-tokens'; } from './authentication.di-tokens';
import { AuthenticationRepository } from './infrastructure/authentication.repository'; import { AuthenticationRepository } from './infrastructure/authentication.repository';
@ -29,8 +27,6 @@ import { ValidateAuthenticationGrpcController } from './interface/grpc-controlle
import { ValidateAuthenticationQueryHandler } from './core/application/queries/validate-authentication/validate-authentication.query-handler'; import { ValidateAuthenticationQueryHandler } from './core/application/queries/validate-authentication/validate-authentication.query-handler';
import { UserUpdatedMessageHandler } from './interface/message-handlers/user-updated.message-handler'; import { UserUpdatedMessageHandler } from './interface/message-handlers/user-updated.message-handler';
import { UserDeletedMessageHandler } from './interface/message-handlers/user-deleted.message-handler'; import { UserDeletedMessageHandler } from './interface/message-handlers/user-deleted.message-handler';
import { ConfigurationRepository } from '@mobicoop/configuration-module';
import { PasswordVerifier } from './infrastructure/password-verifier';
const grpcControllers = [ const grpcControllers = [
CreateAuthenticationGrpcController, CreateAuthenticationGrpcController,
@ -66,10 +62,6 @@ const repositories: Provider[] = [
provide: USERNAME_REPOSITORY, provide: USERNAME_REPOSITORY,
useClass: UsernameRepository, useClass: UsernameRepository,
}, },
{
provide: AUTHENTICATION_CONFIGURATION_REPOSITORY,
useClass: ConfigurationRepository,
},
]; ];
const messagePublishers: Provider[] = [ const messagePublishers: Provider[] = [
@ -79,13 +71,7 @@ const messagePublishers: Provider[] = [
}, },
]; ];
const orms: Provider[] = [ const orms: Provider[] = [PrismaService];
PrismaService,
{
provide: PASSWORD_VERIFIER,
useClass: PasswordVerifier,
},
];
@Module({ @Module({
imports: [CqrsModule], imports: [CqrsModule],

View File

@ -6,50 +6,26 @@ import {
UniqueConstraintException, UniqueConstraintException,
} from '@mobicoop/ddd-library'; } from '@mobicoop/ddd-library';
import { CreateAuthenticationCommand } from './create-authentication.command'; import { CreateAuthenticationCommand } from './create-authentication.command';
import { import { AUTHENTICATION_REPOSITORY } from '@modules/authentication/authentication.di-tokens';
AUTHENTICATION_CONFIGURATION_REPOSITORY,
AUTHENTICATION_REPOSITORY,
} from '@modules/authentication/authentication.di-tokens';
import { AuthenticationRepositoryPort } from '../../ports/authentication.repository.port'; import { AuthenticationRepositoryPort } from '../../ports/authentication.repository.port';
import { AuthenticationEntity } from '@modules/authentication/core/domain/authentication.entity'; import { AuthenticationEntity } from '@modules/authentication/core/domain/authentication.entity';
import { import {
AuthenticationAlreadyExistsException, AuthenticationAlreadyExistsException,
UsernameAlreadyExistsException, UsernameAlreadyExistsException,
} from '@modules/authentication/core/domain/authentication.errors'; } from '@modules/authentication/core/domain/authentication.errors';
import {
Configurator,
Domain,
GetConfigurationRepositoryPort,
} from '@mobicoop/configuration-module';
import {
AUTH_CONFIG_ENCRYPTION_ALGORITHM,
AuthKeyTypes,
} from '@modules/authentication/authentication.constants';
import { EncryptionAlgorithm } from '@modules/authentication/core/domain/username.types';
import { PasswordEncrypter } from '@modules/authentication/infrastructure/password-encrypter';
import { PasswordEncrypterPort } from '../../ports/password-encrypter.port';
@CommandHandler(CreateAuthenticationCommand) @CommandHandler(CreateAuthenticationCommand)
export class CreateAuthenticationService implements ICommandHandler { export class CreateAuthenticationService implements ICommandHandler {
constructor( constructor(
@Inject(AUTHENTICATION_REPOSITORY) @Inject(AUTHENTICATION_REPOSITORY)
private readonly authenticationRepository: AuthenticationRepositoryPort, private readonly authenticationRepository: AuthenticationRepositoryPort,
@Inject(AUTHENTICATION_CONFIGURATION_REPOSITORY)
private readonly configurationRepository: GetConfigurationRepositoryPort,
) {} ) {}
async execute(command: CreateAuthenticationCommand): Promise<AggregateID> { async execute(command: CreateAuthenticationCommand): Promise<AggregateID> {
const authConfigurator: Configurator =
await this.configurationRepository.mget(Domain.AUTH, AuthKeyTypes);
const passwordEncrypter: PasswordEncrypterPort = new PasswordEncrypter(
authConfigurator.get<EncryptionAlgorithm>(
AUTH_CONFIG_ENCRYPTION_ALGORITHM,
),
);
const authentication: AuthenticationEntity = const authentication: AuthenticationEntity =
await AuthenticationEntity.create({ await AuthenticationEntity.create({
userId: command.userId, userId: command.userId,
password: await passwordEncrypter.encrypt(command.password), password: command.password,
usernames: command.usernames, usernames: command.usernames,
}); });
try { try {

View File

@ -1,51 +1,22 @@
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { Inject } from '@nestjs/common'; import { Inject } from '@nestjs/common';
import { AggregateID } from '@mobicoop/ddd-library'; import { AggregateID } from '@mobicoop/ddd-library';
import { import { AUTHENTICATION_REPOSITORY } from '@modules/authentication/authentication.di-tokens';
AUTHENTICATION_CONFIGURATION_REPOSITORY,
AUTHENTICATION_REPOSITORY,
} from '@modules/authentication/authentication.di-tokens';
import { UpdatePasswordCommand } from './update-password.command'; import { UpdatePasswordCommand } from './update-password.command';
import { AuthenticationRepositoryPort } from '../../ports/authentication.repository.port'; import { AuthenticationRepositoryPort } from '../../ports/authentication.repository.port';
import { AuthenticationEntity } from '@modules/authentication/core/domain/authentication.entity'; import { AuthenticationEntity } from '@modules/authentication/core/domain/authentication.entity';
import {
Domain,
Configurator,
GetConfigurationRepositoryPort,
} from '@mobicoop/configuration-module';
import {
AUTH_CONFIG_ENCRYPTION_ALGORITHM,
AuthKeyTypes,
} from '@modules/authentication/authentication.constants';
import { EncryptionAlgorithm } from '@modules/authentication/core/domain/username.types';
import { PasswordEncrypterPort } from '../../ports/password-encrypter.port';
import { PasswordEncrypter } from '@modules/authentication/infrastructure/password-encrypter';
@CommandHandler(UpdatePasswordCommand) @CommandHandler(UpdatePasswordCommand)
export class UpdatePasswordService implements ICommandHandler { export class UpdatePasswordService implements ICommandHandler {
constructor( constructor(
@Inject(AUTHENTICATION_REPOSITORY) @Inject(AUTHENTICATION_REPOSITORY)
private readonly authenticationRepository: AuthenticationRepositoryPort, private readonly authenticationRepository: AuthenticationRepositoryPort,
@Inject(AUTHENTICATION_CONFIGURATION_REPOSITORY)
private readonly configurationRepository: GetConfigurationRepositoryPort,
) {} ) {}
async execute(command: UpdatePasswordCommand): Promise<AggregateID> { async execute(command: UpdatePasswordCommand): Promise<AggregateID> {
const authConfigurator: Configurator =
await this.configurationRepository.mget(Domain.AUTH, AuthKeyTypes);
const encryptionAlgorithm: EncryptionAlgorithm =
authConfigurator.get<EncryptionAlgorithm>(
AUTH_CONFIG_ENCRYPTION_ALGORITHM,
);
const passwordEncrypter: PasswordEncrypterPort = new PasswordEncrypter(
encryptionAlgorithm,
);
const authentication: AuthenticationEntity = const authentication: AuthenticationEntity =
await this.authenticationRepository.findOneById(command.userId); await this.authenticationRepository.findOneById(command.userId);
const encryptedPassword: string = await passwordEncrypter.encrypt( await authentication.updatePassword(command.password);
command.password,
);
await authentication.updatePassword(encryptedPassword);
await this.authenticationRepository.update(command.userId, authentication); await this.authenticationRepository.update(command.userId, authentication);
return authentication.id; return authentication.id;
} }

View File

@ -1,6 +0,0 @@
import { EncryptionAlgorithm } from '../../domain/username.types';
export abstract class PasswordEncrypterPort {
constructor(protected readonly encryptionAlgorithm: EncryptionAlgorithm) {}
abstract encrypt(plainPassword: string): Promise<string>;
}

View File

@ -1,3 +0,0 @@
export interface PasswordVerifierPort {
verify(passwordToVerify: string, encryptedPassword: string): Promise<boolean>;
}

View File

@ -3,7 +3,6 @@ import { Inject, UnauthorizedException } from '@nestjs/common';
import { ValidateAuthenticationQuery } from './validate-authentication.query'; import { ValidateAuthenticationQuery } from './validate-authentication.query';
import { import {
AUTHENTICATION_REPOSITORY, AUTHENTICATION_REPOSITORY,
PASSWORD_VERIFIER,
USERNAME_REPOSITORY, USERNAME_REPOSITORY,
} from '@modules/authentication/authentication.di-tokens'; } from '@modules/authentication/authentication.di-tokens';
import { AuthenticationRepositoryPort } from '../../ports/authentication.repository.port'; import { AuthenticationRepositoryPort } from '../../ports/authentication.repository.port';
@ -11,7 +10,6 @@ import { UsernameRepositoryPort } from '../../ports/username.repository.port';
import { AuthenticationEntity } from '@modules/authentication/core/domain/authentication.entity'; import { AuthenticationEntity } from '@modules/authentication/core/domain/authentication.entity';
import { UsernameEntity } from '@modules/authentication/core/domain/username.entity'; import { UsernameEntity } from '@modules/authentication/core/domain/username.entity';
import { AggregateID, NotFoundException } from '@mobicoop/ddd-library'; import { AggregateID, NotFoundException } from '@mobicoop/ddd-library';
import { PasswordVerifierPort } from '../../ports/password-verifier.port';
@QueryHandler(ValidateAuthenticationQuery) @QueryHandler(ValidateAuthenticationQuery)
export class ValidateAuthenticationQueryHandler implements IQueryHandler { export class ValidateAuthenticationQueryHandler implements IQueryHandler {
@ -20,8 +18,6 @@ export class ValidateAuthenticationQueryHandler implements IQueryHandler {
private readonly authenticationRepository: AuthenticationRepositoryPort, private readonly authenticationRepository: AuthenticationRepositoryPort,
@Inject(USERNAME_REPOSITORY) @Inject(USERNAME_REPOSITORY)
private readonly usernameRepository: UsernameRepositoryPort, private readonly usernameRepository: UsernameRepositoryPort,
@Inject(PASSWORD_VERIFIER)
private readonly passwordVerifier: PasswordVerifierPort,
) {} ) {}
execute = async ( execute = async (
@ -44,7 +40,6 @@ export class ValidateAuthenticationQueryHandler implements IQueryHandler {
try { try {
const isAuthenticated = await authenticationEntity.authenticate( const isAuthenticated = await authenticationEntity.authenticate(
query.password, query.password,
this.passwordVerifier,
); );
if (isAuthenticated) return authenticationEntity.id; if (isAuthenticated) return authenticationEntity.id;
throw new UnauthorizedException(); throw new UnauthorizedException();

View File

@ -1,4 +1,5 @@
import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library'; import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
import * as bcrypt from 'bcrypt';
import { import {
AuthenticationProps, AuthenticationProps,
CreateAuthenticationProps, CreateAuthenticationProps,
@ -6,7 +7,6 @@ import {
import { AuthenticationCreatedDomainEvent } from './events/authentication-created.domain-event'; import { AuthenticationCreatedDomainEvent } from './events/authentication-created.domain-event';
import { AuthenticationDeletedDomainEvent } from './events/authentication-deleted.domain-event'; import { AuthenticationDeletedDomainEvent } from './events/authentication-deleted.domain-event';
import { PasswordUpdatedDomainEvent } from './events/password-updated.domain-event'; import { PasswordUpdatedDomainEvent } from './events/password-updated.domain-event';
import { PasswordVerifierPort } from '../application/ports/password-verifier.port';
export class AuthenticationEntity extends AggregateRoot<AuthenticationProps> { export class AuthenticationEntity extends AggregateRoot<AuthenticationProps> {
protected readonly _id: AggregateID; protected readonly _id: AggregateID;
@ -15,11 +15,12 @@ export class AuthenticationEntity extends AggregateRoot<AuthenticationProps> {
create: CreateAuthenticationProps, create: CreateAuthenticationProps,
): Promise<AuthenticationEntity> => { ): Promise<AuthenticationEntity> => {
const props: AuthenticationProps = { ...create }; const props: AuthenticationProps = { ...create };
const hash = await AuthenticationEntity.encryptPassword(props.password);
const authentication = new AuthenticationEntity({ const authentication = new AuthenticationEntity({
id: props.userId, id: props.userId,
props: { props: {
userId: props.userId, userId: props.userId,
password: props.password, password: hash,
usernames: props.usernames, usernames: props.usernames,
}, },
}); });
@ -30,7 +31,7 @@ export class AuthenticationEntity extends AggregateRoot<AuthenticationProps> {
}; };
updatePassword = async (password: string): Promise<void> => { updatePassword = async (password: string): Promise<void> => {
this.props.password = password; this.props.password = await AuthenticationEntity.encryptPassword(password);
this.addEvent( this.addEvent(
new PasswordUpdatedDomainEvent({ new PasswordUpdatedDomainEvent({
aggregateId: this.id, aggregateId: this.id,
@ -46,13 +47,13 @@ export class AuthenticationEntity extends AggregateRoot<AuthenticationProps> {
); );
} }
authenticate = async ( authenticate = async (password: string): Promise<boolean> =>
password: string, await bcrypt.compare(password, this.props.password);
passwordVerifier: PasswordVerifierPort,
): Promise<boolean> =>
await passwordVerifier.verify(password, this.props.password);
validate(): void { validate(): void {
// entity business rules validation to protect it's invariant before saving entity to a database // entity business rules validation to protect it's invariant before saving entity to a database
} }
private static encryptPassword = async (password: string): Promise<string> =>
await bcrypt.hash(password, 10);
} }

View File

@ -19,10 +19,3 @@ export enum Type {
EMAIL = 'EMAIL', EMAIL = 'EMAIL',
PHONE = 'PHONE', PHONE = 'PHONE',
} }
export enum EncryptionAlgorithm {
BCRYPT = 'BCRYPT',
ARGON2I = 'ARGON2I',
ARGON2D = 'ARGON2D',
ARGON2ID = 'ARGON2ID',
}

View File

@ -1,35 +0,0 @@
import { Injectable } from '@nestjs/common';
import { EncryptionAlgorithm } from '../core/domain/username.types';
import * as bcrypt from 'bcrypt';
import * as argon2 from 'argon2';
import { PasswordEncrypterPort } from '../core/application/ports/password-encrypter.port';
@Injectable()
export class PasswordEncrypter extends PasswordEncrypterPort {
encrypt = async (plainPassword: string): Promise<string> => {
switch (this.encryptionAlgorithm) {
case EncryptionAlgorithm.BCRYPT:
return await bcrypt.hash(plainPassword, 10);
case EncryptionAlgorithm.ARGON2D:
case EncryptionAlgorithm.ARGON2I:
case EncryptionAlgorithm.ARGON2ID:
return await argon2.hash(plainPassword, {
type: this.argonType(),
});
}
};
private argonType = ():
| typeof argon2.argon2d
| typeof argon2.argon2i
| typeof argon2.argon2id => {
switch (this.encryptionAlgorithm) {
case EncryptionAlgorithm.ARGON2D:
return argon2.argon2d;
case EncryptionAlgorithm.ARGON2I:
return argon2.argon2i;
default:
return argon2.argon2id;
}
};
}

View File

@ -1,51 +0,0 @@
import { Injectable } from '@nestjs/common';
import { EncryptionAlgorithm } from '../core/domain/username.types';
import * as bcrypt from 'bcrypt';
import * as argon2 from 'argon2';
import { PasswordVerifierPort } from '../core/application/ports/password-verifier.port';
@Injectable()
export class PasswordVerifier implements PasswordVerifierPort {
verify = async (
passwordToVerify: string,
encryptedPassword: string,
): Promise<boolean> => {
const encryptionAlgorithm: EncryptionAlgorithm =
this.guessEncryptionAlgorithm(encryptedPassword);
switch (encryptionAlgorithm) {
case EncryptionAlgorithm.BCRYPT:
return await bcrypt.compare(passwordToVerify, encryptedPassword);
case EncryptionAlgorithm.ARGON2D:
case EncryptionAlgorithm.ARGON2I:
case EncryptionAlgorithm.ARGON2ID:
return await argon2.verify(encryptedPassword, passwordToVerify, {
type: this.argonType(encryptionAlgorithm),
});
}
};
private guessEncryptionAlgorithm = (
password: string,
): EncryptionAlgorithm => {
if (password.substring(1, 9) === 'argon2id')
return EncryptionAlgorithm.ARGON2ID;
if (password.substring(1, 8) === 'argon2i')
return EncryptionAlgorithm.ARGON2I;
if (password.substring(1, 8) === 'argon2d')
return EncryptionAlgorithm.ARGON2D;
return EncryptionAlgorithm.BCRYPT;
};
private argonType = (
encryptionAlgorithm: EncryptionAlgorithm,
): typeof argon2.argon2d | typeof argon2.argon2i | typeof argon2.argon2id => {
switch (encryptionAlgorithm) {
case EncryptionAlgorithm.ARGON2D:
return argon2.argon2d;
case EncryptionAlgorithm.ARGON2I:
return argon2.argon2i;
default:
return argon2.argon2id;
}
};
}

View File

@ -1,4 +1,3 @@
import { PasswordVerifierPort } from '@modules/authentication/core/application/ports/password-verifier.port';
import { AuthenticationEntity } from '@modules/authentication/core/domain/authentication.entity'; import { AuthenticationEntity } from '@modules/authentication/core/domain/authentication.entity';
import { CreateAuthenticationProps } from '@modules/authentication/core/domain/authentication.types'; import { CreateAuthenticationProps } from '@modules/authentication/core/domain/authentication.types';
import { AuthenticationDeletedDomainEvent } from '@modules/authentication/core/domain/events/authentication-deleted.domain-event'; import { AuthenticationDeletedDomainEvent } from '@modules/authentication/core/domain/events/authentication-deleted.domain-event';
@ -31,10 +30,6 @@ const createAuthenticationPropsWith2Usernames: CreateAuthenticationProps = {
], ],
}; };
const mockPasswordVerifier: PasswordVerifierPort = {
verify: jest.fn().mockResolvedValueOnce(true).mockResolvedValueOnce(false),
};
describe('Authentication entity create', () => { describe('Authentication entity create', () => {
it('should create a new authentication entity', async () => { it('should create a new authentication entity', async () => {
const authenticationEntity: AuthenticationEntity = const authenticationEntity: AuthenticationEntity =
@ -42,6 +37,7 @@ describe('Authentication entity create', () => {
expect(authenticationEntity.id).toBe( expect(authenticationEntity.id).toBe(
'165192d4-398a-4469-a16b-98c02cc6f531', '165192d4-398a-4469-a16b-98c02cc6f531',
); );
expect(authenticationEntity.getProps().password.length).toBe(60);
expect(authenticationEntity.domainEvents.length).toBe(1); expect(authenticationEntity.domainEvents.length).toBe(1);
}); });
it('should create a new authentication entity with 2 usernames', async () => { it('should create a new authentication entity with 2 usernames', async () => {
@ -84,19 +80,15 @@ describe('Authentication password validation', () => {
it('should validate a valid password', async () => { it('should validate a valid password', async () => {
const authenticationEntity: AuthenticationEntity = const authenticationEntity: AuthenticationEntity =
await AuthenticationEntity.create(createAuthenticationProps); await AuthenticationEntity.create(createAuthenticationProps);
const result: boolean = await authenticationEntity.authenticate( const result: boolean =
'somePassword', await authenticationEntity.authenticate('somePassword');
mockPasswordVerifier,
);
expect(result).toBeTruthy(); expect(result).toBeTruthy();
}); });
it('should not validate an invalid password', async () => { it('should not validate an invalid password', async () => {
const authenticationEntity: AuthenticationEntity = const authenticationEntity: AuthenticationEntity =
await AuthenticationEntity.create(createAuthenticationProps); await AuthenticationEntity.create(createAuthenticationProps);
const result: boolean = await authenticationEntity.authenticate( const result: boolean =
'someWrongPassword', await authenticationEntity.authenticate('someWrongPassword');
mockPasswordVerifier,
);
expect(result).toBeFalsy(); expect(result).toBeFalsy();
}); });
}); });

View File

@ -1,19 +1,9 @@
import {
Domain,
Configurator,
GetConfigurationRepositoryPort,
KeyType,
} from '@mobicoop/configuration-module';
import { import {
AggregateID, AggregateID,
ConflictException, ConflictException,
UniqueConstraintException, UniqueConstraintException,
} from '@mobicoop/ddd-library'; } from '@mobicoop/ddd-library';
import { AUTH_CONFIG_ENCRYPTION_ALGORITHM } from '@modules/authentication/authentication.constants'; import { AUTHENTICATION_REPOSITORY } from '@modules/authentication/authentication.di-tokens';
import {
AUTHENTICATION_CONFIGURATION_REPOSITORY,
AUTHENTICATION_REPOSITORY,
} from '@modules/authentication/authentication.di-tokens';
import { CreateAuthenticationCommand } from '@modules/authentication/core/application/commands/create-authentication/create-authentication.command'; import { CreateAuthenticationCommand } from '@modules/authentication/core/application/commands/create-authentication/create-authentication.command';
import { CreateAuthenticationService } from '@modules/authentication/core/application/commands/create-authentication/create-authentication.service'; import { CreateAuthenticationService } from '@modules/authentication/core/application/commands/create-authentication/create-authentication.service';
import { AuthenticationEntity } from '@modules/authentication/core/domain/authentication.entity'; import { AuthenticationEntity } from '@modules/authentication/core/domain/authentication.entity';
@ -54,25 +44,6 @@ const mockAuthenticationRepository = {
}), }),
}; };
const mockConfigurationRepository: GetConfigurationRepositoryPort = {
get: jest.fn(),
mget: jest.fn().mockImplementation(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
(domain: Domain, keyTypes: KeyType[]) => {
switch (domain) {
case Domain.AUTH:
return new Configurator(Domain.AUTH, [
{
domain: Domain.AUTH,
key: AUTH_CONFIG_ENCRYPTION_ALGORITHM,
value: 'BCRYPT',
},
]);
}
},
),
};
describe('Create Authentication Service', () => { describe('Create Authentication Service', () => {
let createAuthenticationService: CreateAuthenticationService; let createAuthenticationService: CreateAuthenticationService;
@ -83,10 +54,6 @@ describe('Create Authentication Service', () => {
provide: AUTHENTICATION_REPOSITORY, provide: AUTHENTICATION_REPOSITORY,
useValue: mockAuthenticationRepository, useValue: mockAuthenticationRepository,
}, },
{
provide: AUTHENTICATION_CONFIGURATION_REPOSITORY,
useValue: mockConfigurationRepository,
},
CreateAuthenticationService, CreateAuthenticationService,
], ],
}).compile(); }).compile();

View File

@ -1,20 +1,10 @@
import { AggregateID } from '@mobicoop/ddd-library'; import { AggregateID } from '@mobicoop/ddd-library';
import { import { AUTHENTICATION_REPOSITORY } from '@modules/authentication/authentication.di-tokens';
AUTHENTICATION_CONFIGURATION_REPOSITORY,
AUTHENTICATION_REPOSITORY,
} from '@modules/authentication/authentication.di-tokens';
import { UpdatePasswordService } from '@modules/authentication/core/application/commands/update-password/update-password.service'; import { UpdatePasswordService } from '@modules/authentication/core/application/commands/update-password/update-password.service';
import { Type } from '@modules/authentication/core/domain/username.types'; import { Type } from '@modules/authentication/core/domain/username.types';
import { UpdatePasswordRequestDto } from '@modules/authentication/interface/grpc-controllers/dtos/update-password.request.dto'; import { UpdatePasswordRequestDto } from '@modules/authentication/interface/grpc-controllers/dtos/update-password.request.dto';
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
import { UpdatePasswordCommand } from '@modules/authentication/core/application/commands/update-password/update-password.command'; import { UpdatePasswordCommand } from '@modules/authentication/core/application/commands/update-password/update-password.command';
import {
Domain,
Configurator,
GetConfigurationRepositoryPort,
KeyType,
} from '@mobicoop/configuration-module';
import { AUTH_CONFIG_ENCRYPTION_ALGORITHM } from '@modules/authentication/authentication.constants';
const updatePasswordRequest: UpdatePasswordRequestDto = { const updatePasswordRequest: UpdatePasswordRequestDto = {
userId: '165192d4-398a-4469-a16b-98c02cc6f531', userId: '165192d4-398a-4469-a16b-98c02cc6f531',
@ -34,25 +24,6 @@ const mockAuthenticationRepository = {
update: jest.fn().mockImplementation(), update: jest.fn().mockImplementation(),
}; };
const mockConfigurationRepository: GetConfigurationRepositoryPort = {
get: jest.fn(),
mget: jest.fn().mockImplementation(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
(domain: Domain, keyTypes: KeyType[]) => {
switch (domain) {
case Domain.AUTH:
return new Configurator(Domain.AUTH, [
{
domain: Domain.AUTH,
key: AUTH_CONFIG_ENCRYPTION_ALGORITHM,
value: 'BCRYPT',
},
]);
}
},
),
};
describe('Update Password Service', () => { describe('Update Password Service', () => {
let updatePasswordService: UpdatePasswordService; let updatePasswordService: UpdatePasswordService;
@ -63,10 +34,6 @@ describe('Update Password Service', () => {
provide: AUTHENTICATION_REPOSITORY, provide: AUTHENTICATION_REPOSITORY,
useValue: mockAuthenticationRepository, useValue: mockAuthenticationRepository,
}, },
{
provide: AUTHENTICATION_CONFIGURATION_REPOSITORY,
useValue: mockConfigurationRepository,
},
UpdatePasswordService, UpdatePasswordService,
], ],
}).compile(); }).compile();

View File

@ -1,10 +1,8 @@
import { AggregateID, NotFoundException } from '@mobicoop/ddd-library'; import { AggregateID, NotFoundException } from '@mobicoop/ddd-library';
import { import {
AUTHENTICATION_REPOSITORY, AUTHENTICATION_REPOSITORY,
PASSWORD_VERIFIER,
USERNAME_REPOSITORY, USERNAME_REPOSITORY,
} from '@modules/authentication/authentication.di-tokens'; } from '@modules/authentication/authentication.di-tokens';
import { PasswordVerifierPort } from '@modules/authentication/core/application/ports/password-verifier.port';
import { ValidateAuthenticationQuery } from '@modules/authentication/core/application/queries/validate-authentication/validate-authentication.query'; import { ValidateAuthenticationQuery } from '@modules/authentication/core/application/queries/validate-authentication/validate-authentication.query';
import { ValidateAuthenticationQueryHandler } from '@modules/authentication/core/application/queries/validate-authentication/validate-authentication.query-handler'; import { ValidateAuthenticationQueryHandler } from '@modules/authentication/core/application/queries/validate-authentication/validate-authentication.query-handler';
import { UnauthorizedException } from '@nestjs/common'; import { UnauthorizedException } from '@nestjs/common';
@ -52,10 +50,6 @@ const mockAuthenticationRepository = {
}), }),
}; };
const mockPasswordVerifier: PasswordVerifierPort = {
verify: jest.fn().mockResolvedValueOnce(true).mockResolvedValueOnce(false),
};
describe('Validate Authentication Query Handler', () => { describe('Validate Authentication Query Handler', () => {
let validateAuthenticationQueryHandler: ValidateAuthenticationQueryHandler; let validateAuthenticationQueryHandler: ValidateAuthenticationQueryHandler;
@ -70,10 +64,6 @@ describe('Validate Authentication Query Handler', () => {
provide: USERNAME_REPOSITORY, provide: USERNAME_REPOSITORY,
useValue: mockUsernameRepository, useValue: mockUsernameRepository,
}, },
{
provide: PASSWORD_VERIFIER,
useValue: mockPasswordVerifier,
},
ValidateAuthenticationQueryHandler, ValidateAuthenticationQueryHandler,
], ],
}).compile(); }).compile();

View File

@ -1,41 +0,0 @@
import { PasswordEncrypterPort } from '@modules/authentication/core/application/ports/password-encrypter.port';
import { EncryptionAlgorithm } from '@modules/authentication/core/domain/username.types';
import { PasswordEncrypter } from '@modules/authentication/infrastructure/password-encrypter';
describe('Password encrypter', () => {
it('should encrypt a password in bcrypt', async () => {
const passwordEncrypter: PasswordEncrypterPort = new PasswordEncrypter(
EncryptionAlgorithm.BCRYPT,
);
const encryptedPassword: string =
await passwordEncrypter.encrypt('somePassword');
expect(encryptedPassword.substring(0, 2)).toBe('$2');
});
it('should encrypt a password in argon2i', async () => {
const passwordEncrypter: PasswordEncrypterPort = new PasswordEncrypter(
EncryptionAlgorithm.ARGON2I,
);
const encryptedPassword: string =
await passwordEncrypter.encrypt('somePassword');
expect(encryptedPassword.substring(0, 9)).toBe('$argon2i$');
});
it('should encrypt a password in argon2d', async () => {
const passwordEncrypter: PasswordEncrypterPort = new PasswordEncrypter(
EncryptionAlgorithm.ARGON2D,
);
const encryptedPassword: string =
await passwordEncrypter.encrypt('somePassword');
expect(encryptedPassword.substring(0, 9)).toBe('$argon2d$');
});
it('should encrypt a password in argon2id', async () => {
const passwordEncrypter: PasswordEncrypterPort = new PasswordEncrypter(
EncryptionAlgorithm.ARGON2ID,
);
const encryptedPassword: string =
await passwordEncrypter.encrypt('somePassword');
expect(encryptedPassword.substring(0, 10)).toBe('$argon2id$');
});
});

View File

@ -1,46 +0,0 @@
import { PasswordVerifierPort } from '@modules/authentication/core/application/ports/password-verifier.port';
import { PasswordVerifier } from '@modules/authentication/infrastructure/password-verifier';
describe('Password verifier', () => {
const passwordVerifier: PasswordVerifierPort = new PasswordVerifier();
it('should verify a bcrypt password', async () => {
const bcryptEncryptedPassword: string =
'$2b$10$IGMj7/tH66kzO8rqwdogK.tgY2nFOdtMC.dzMAYUVfbSTPwWk7sk.';
const verified: boolean = await passwordVerifier.verify(
'somePassword',
bcryptEncryptedPassword,
);
expect(verified).toBeTruthy();
});
it('should verify an argon2i password', async () => {
const argon2iEncryptedPassword: string =
'$argon2i$v=19$m=16,t=2,p=1$c2lERkU0UmpMYkhSa1h3eQ$hSyr7TZZDzvq9XJgA+D/pw';
const verified: boolean = await passwordVerifier.verify(
'somePassword',
argon2iEncryptedPassword,
);
expect(verified).toBeTruthy();
});
it('should verify an argon2d password', async () => {
const argon2dEncryptedPassword: string =
'$argon2d$v=19$m=16,t=2,p=1$c2lERkU0UmpMYkhSa1h3eQ$YBopyKamOUl7ZPhu+KPASw';
const verified: boolean = await passwordVerifier.verify(
'somePassword',
argon2dEncryptedPassword,
);
expect(verified).toBeTruthy();
});
it('should verify an argon2id password', async () => {
const argon2idEncryptedPassword: string =
'$argon2id$v=19$m=16,t=2,p=1$c2lERkU0UmpMYkhSa1h3eQ$Heokt0Lk6mtBmRE07q94lw';
const verified: boolean = await passwordVerifier.verify(
'somePassword',
argon2idEncryptedPassword,
);
expect(verified).toBeTruthy();
});
});

View File

@ -1,7 +0,0 @@
import { repl } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
await repl(AppModule);
}
bootstrap();