Third part, authentication based on clean architecture
Published: 2021-11-09 15:52:36

#### _Third part..._

In the previous post we made the use case to authenticate ourselves in the system, now we are going to solve a use case to read all the users that exist in the system and include the roles so they can access this resource.

1. Use case to read the system users

We create the gateway for this use case.

```
scaffold create:interface --name=get-users --path=models
```

We create this interface to then apply the principle of Dependency Inversion, between the service and the Mongo DB adapter.

_src/domain/models/gateways/get-users-repository.ts_

```typescript
import {UserModel} from "@/domain/models/user";

export const GET_USERS_REPOSITORY = "GET_USERS_REPOSITORY";

export interface IGetUsersRepository {
    getUsersRepository: () => Promise<UserModel[]>
}
```

Now we create the service that will implement the logic of the use case.

```
scaffold create:service --name=get-users
```
_src/domain/use-cases/get-users-service.ts_

We create this interface to then apply the principle of Dependency Inversion, between the service and the entry point.

```typescript
import {UserModel} from "@/domain/models/user";

export const GET_USERS_SERVICE = "GET_USERS_SERVICE";

export interface IGetUsersService {
    getUsersService: () => Promise<UserModel[]>
}
```

_src/domain/use-cases/impl/get-users-service-impl.ts_

```typescript
import {Adapter, Service} from "@tsclean/core";

@Service()
export class GetUsersServiceImpl implements IGetUsersService {
    constructor(
        @Adapter(GET_USERS_REPOSITORY) private readonly getUsersRepository: IGetUsersRepository
    ) {
    }

    async getUsersService(): Promise<UserModel[]> {
        return await this.getUsersRepository.getUsersRepository();
    }
}
```

We already have everything we need in the domain layer for the use case, now we are going to include the interface in the mongo adapter we have.

_src/infrastructure/driven-adapters/orm/mongoose/user-mongoose-repository-adapter.ts_

```typescript
import {AddUserParams, UserModel} from "@/domain/models/user";
import {UserModelSchema} from "@/infrastructure/driven-adapters/adapters/orm/mongoose/models/user";
import {IAddUserRepository} from "@/domain/models/gateways/add-user-repository";
import {ICheckEmailRepository} from "@/domain/models/gateways/check-email-repository";

export class UserMongooseRepositoryAdapter implements IAddUserRepository,
ICheckEmailRepository,
IGetUsersRepository {

// We create this function to manage the entity that exists in the domain.
    map(data: any): any {
        const {_id, firstName, lastName, email, password} = data
        return Object.assign({}, {id: _id.toString(), firstName, lastName, email, password})
    }

    async addUserRepository(data: AddUserParams): Promise<UserModel> {
        return await UserModelSchema.create(data);
    }

    async checkEmail(email: string): Promise<ICheckEmailRepository.Result> {
        const user = await UserModelSchema.findOne({email}).exec();
        return user && this.map(user);
    }

    async getUsersRepository(): Promise<UserModel[]> {
        return UserModelSchema.find().select("-password");
    }
}
```
We create the controller as an entry point.

```
scaffold create:controller --name=get-users
```

_src/infrastructure/entry-points/api/get-users-controller.ts_

```typescript
import {Adapter, Get, Mapping} from "@tsclean/core";
import {GET_USERS_SERVICE, IGetUsersService} from "@/domain/use-cases/get-users-service";

@Mapping('api/v1/get-users')
export class GetUsersController {

    constructor(
        @Adapter(GET_USERS_SERVICE) private readonly getUsersService: IGetUsersService
    ) {
    }

    @Get()
    async getUsersController(): Promise<any> {
        return await this.getUsersService.getUsersService();
    }
}
```

If everything went well and you have reached this point when you visit the endpoint http://localhost:9000/api/v1/get-users, it should users list.

2. Protecting resources with roles.

We have already solved our use cases, now we are going to protect some resources so that only users with a certain role can access them. To achieve this we must first go to the adapter and create the corresponding logic.

_src/infrastructure/driven-adapters/adapters/jwt-adapter.ts_

```typescript
import jwt from "jsonwebtoken";
import dotenv from "dotenv";
import {IEncrypt} from "@/domain/models/gateways/encrypt-repository";
import {AccessResourceInterface, ExecutionContextInterface} from "@tsclean/core";

dotenv.config({path: ".env"})

export class JwtAdapter implements IEncrypt, AccessResourceInterface {

    constructor(
        private readonly roles: string[]
    ) {
    }

    async encrypt(text: string | number | Buffer, roles: []): Promise<string> {
        const payload = {id: text, roles: roles}
        return jwt.sign({account: payload}, process.env.JWT_SECRET, {expiresIn: "1d"});
    }

    accessResource(context: ExecutionContextInterface): boolean | Promise<boolean> {
        try {
            const request = context.getHttp().getRequest();
            const token = request.rawHeaders[1].split(" ")[1];

            if (token) {
                const decode = jwt.verify(token, process.env.JWT_SECRET);
                const roles = decode["account"].roles;

                let assignRole = false;

                for (const role of roles) {
                    assignRole = false;
                    for (const roleElement of this.roles) {
                        let roleExist = role.role === roleElement;
                        if (roleExist) assignRole = roleExist;
                        if (assignRole) return true;
                    }
                }
            }
        } catch (e) {
            return false;
        }
    }
}
```

What we do in this adapter is to implement the AccessResourceInterface interface provided by the plugin with its accessResource method.  We receive through the constructor the roles of the user that tries to access the resource. And a boolean is returned according to the validation.

Next we create a factory that will help us in the controller with this validation.

_src/infrastructure/helpers/auth.ts_

```typescript
import {applyDecorators, AccessResource} from "@tsclean/core";
import {JwtAdapter} from "@/infrastructure/driven-adapters/adapters/jwt-adapter";

export function Auth(roles: string[]) {
    return applyDecorators(AccessResource(new JwtAdapter(roles)));
}
```

_src/infrastructure/entry-points/api/get-users-controller.ts_

```typescript
import {Adapter, Get, Mapping} from "@tsclean/core";
import {Auth} from "@/infrastructure/helpers/auth";
import {GET_USERS_SERVICE, IGetUsersService} from "@/domain/use-cases/get-users-service";

@Mapping('api/v1/get-users')
export class GetUsersController {

    constructor(
        @Adapter(GET_USERS_SERVICE) private readonly getUsersService: IGetUsersService
    ) {
    }

    @Get()
    @Auth(["manager", "guest"])
    async getUsersController(): Promise<any> {
        return await this.getUsersService.getUsersService();
    }
}
```

Now you can see that the method has an additional decorator that receives an array of the roles that can access the resource. This resource can have one or many roles.

When a user tries to access the resource with a role different from those in the decorator, it returns a 403 status code. With this, we can protect the ends in a simple way.

If you have reached this point, you have the way to create the authentication for a system and protect the routes with roles and all this applying a concept based on Clean Architecture.

In this link you can find the implementation of the project [https://github.com/tsclean/api-example]
(https://github.com/tsclean/api-example)