Second part...
In the previous post we did the use case for registering a user. In this occasion we are going to continue developing the other use case which is that the user that is registered can authenticate to enter the application. To generate the token we will use the external library jsonwebtoken.
We must create two interfaces to communicate with the infrastructure layer adapters. One to compare that the password corresponds to the database and the other to generate the token from the service.
src/domain/models/gateways/hash-compare-repository.ts
export const HASH_COMPARE_REPOSITORY = "HASH_COMPARE_REPOSITORY";
export interface IHashCompare {
compare: (text: string, verify: string) => Promise<boolean>
}
src/domain/models/gateways/encrypt-repository.ts
export const ENCRYPT_REPOSITORY = "ENCRYPT_REPOSITORY";
export interface IEncrypt {
encrypt: (text: string | number | Buffer) => Promise<IEncrypt.Result>
}
export namespace IEncrypt {
export type Result = string;
}
We create the service to make the use case logic.
scaffold create:service --name=authentication
src/domain/use-cases/authentication-service.ts
export const AUTHENTICATION_SERVICE = "AUTHENTICATION_SERVICE";
export interface IAuthenticationService {
auth: (data: IAuthenticationService.Params) => Promise<IAuthenticationService.Result>;
}
export namespace IAuthenticationService {
export type Params = {
email: string;
password: string
}
export type Result = {
accessToken: string;
name: string
}
}
src/domain/use-cases/impl/authentication-service-impl.ts
import {Adapter, Service} from "@tsclean/core";
import {IAuthenticationService} from "@/domain/use-cases/authentication-service";
import {CHECK_EMAIL_REPOSITORY, ICheckEmailRepository} from "@/domain/models/gateways/check-email-repository";
import {HASH_COMPARE_REPOSITORY, IHashCompare} from "@/domain/models/gateways/hash-compare-repository";
import {ENCRYPT_REPOSITORY, IEncrypt} from "@/domain/models/gateways/encrypt-repository";
@Service()
export class AuthenticationServiceImpl implements IAuthenticationService {
constructor(
@Adapter(ENCRYPT_REPOSITORY) private readonly encrypt: IEncrypt,
@Adapter(HASH_COMPARE_REPOSITORY) private readonly hashCompare: IHashCompare,
@Adapter(CHECK_EMAIL_REPOSITORY) private readonly checkEmailRepository: ICheckEmailRepository) {
}
async auth(data: IAuthenticationService.Params): Promise<IAuthenticationService.Result> {
const account = await this.checkEmailRepository.checkEmail(data.email);
const isValid = await this.hashCompare.compare(data.password, account.password);
if (isValid) {
const accessToken = await this.encrypt.encrypt(account.id);
return {
accessToken,
name: account.firstName
}
}
return null;
}
}
After this we move to the infrasctructure layer and modify the bcrypt adapter and create the adapter for the external library jsonwebtoken.
src/infrasctructure/driven-adapters/bcrypt-adapter.ts
import bcrypt from "bcrypt";
import {IHashCompare} from "@/domain/models/gateways/hash-compare-repository";
import {IHashRepository} from "@/domain/models/gateways/hash-repository";
export class BcryptAdapter implements IHashRepository, IHashCompare {
private readonly salt: number = 12;
constructor() {
}
async compare(text: string, verify: string): Promise<boolean> {
return await bcrypt.compare(text, verify);
}
async hash(text: string): Promise<string> {
return await bcrypt.hash(text, this.salt);
}
}
src/infrasctructure/driven-adapters/jwt-adapter.ts
import jwt from "jsonwebtoken";
import {IEncrypt} from "@/domain/models/gateways/encrypt-repository";
export const secret = "my_secret";
export class JwtAdapter implements IEncrypt {
async encrypt(text: string | number | Buffer): Promise<IEncrypt.Result> {
return jwt.sign({account: text}, secret, {expiresIn: "1d"});
}
}
We must add the adapter to the supplier file so that the dependencies in the main container will be resolved. The updated file should look like this
_src/infrastructure/driven-adapters/providers/index.ts
import {AddUserServiceImpl} from "@/domain/use-cases/impl/add-user-service-impl";
import {AuthenticationServiceImpl} from "@/domain/use-cases/impl/authentication-service-impl";
import {JwtAdapter} from "@/infrastructure/driven-adapters/adapters/jwt-adapter";
import {BcryptAdapter} from "@/infrastructure/driven-adapters/adapters/bcrypt-adapter";
import {UserMongooseRepositoryAdapter} from "@/infrastructure/driven-adapters/adapters/orm/mongoose/user-mongoose-repository-adapter";
import {ADD_USER_REPOSITORY} from "@/domain/models/gateways/add-user-repository";
import {CHECK_EMAIL_REPOSITORY} from "@/domain/models/gateways/check-email-repository";
import {ADD_USER_SERVICE} from "@/domain/use-cases/add-user-service";
import {HASH_COMPARE_REPOSITORY} from "@/domain/models/gateways/hash-compare-repository";
import {AUTHENTICATION_SERVICE} from "@/domain/use-cases/authentication-service";
import {HASH_REPOSITORY} from "@/domain/models/gateways/hash-repository";
import {ENCRYPT_REPOSITORY} from "@/domain/models/gateways/encrypt-repository";
export const adapters = [
{
classAdapter: BcryptAdapter,
key: HASH_REPOSITORY
},
{
classAdapter: UserMongooseRepositoryAdapter,
key: ADD_USER_REPOSITORY
},
{
classAdapter: UserMongooseRepositoryAdapter,
key: CHECK_EMAIL_REPOSITORY
},
{
classAdapter: BcryptAdapter,
key: HASH_COMPARE_REPOSITORY
},
{
classAdapter: JwtAdapter,
key: ENCRYPT_REPOSITORY
}
]
export const services = [
{
classAdapter: AddUserServiceImpl,
key: ADD_USER_SERVICE
},
{
classAdapter: AuthenticationServiceImpl,
key: AUTHENTICATION_SERVICE
}
]
We create the entry point for authentication and then we must add it to the index.ts to export it to the main container
scaffold create:controller --name=authentication
src/infrastructure/entry-points/api/authentication-controller.ts
import {Mapping, Inject, Post, Body, Adapter} from "@tsclean/core";
import {AUTHENTICATION_SERVICE, IAuthenticationService} from "@/domain/use-cases/authentication-service";
import {ValidateFields} from "@/infrastructure/helpers/validate-fields";
@Mapping('api/v1/authentication')
export class AuthenticationController {
constructor(
@Adapter(AUTHENTICATION_SERVICE) private readonly authenticationService: IAuthenticationService
) {
}
@Post()
async authController(@Body() data: IAuthenticationService.Params): Promise<IAuthenticationService.Result | any> {
const {errors, isValid} = ValidateFields.fieldsValidation(data);
if (!isValid) return {statusCode: 422, body: {"message": errors}}
const result = await this.authenticationService.auth(data);
return {
accessToken: result.accessToken,
name: result.name
}
}
}
If everything went well and you have reached this point when you visit the endpoint http://localhost:9000/api/v1/authentication, it should generate the token.
next third part...
https://dev.to/japhernandez/authentication-based-on-clean-architecture-39np
Top comments (0)