Skip to main content

Guarding Routes Continued

Update Create User

Update the create user action to satisfy the contract we updated in authentication part 1.

Now, we should return a token that has encoded the id and email of the created user. That also means, we need to inject the tokenService.

src/application/user/create-user-command.ts
import {
injectService,
ACTION_RESULT,
MediatorResultSuccess,
MediatorResultFailure,
type ITokenService,
} from '@lindeneg/funkallero';
import SERVICE from '@/enums/service';
import BaseAction from '@/application/base-action';
import type {
ICreateUserDto,
ICreateUserResponse,
} from '@/contracts/create-user';

class CreateUserCommand extends BaseAction {
@injectService(SERVICE.TOKEN)
private readonly tokenService: ITokenService;

public async execute(dto: ICreateUserDto) {
const user = await this.dataContext.createUser(dto);

if (!user) {
return new MediatorResultFailure(
ACTION_RESULT.ERROR_INTERNAL_ERROR
);
}

const userResponse: ICreateUserResponse = {
id: user.id,
token: await this.tokenService.createToken({
id: user.id,
email: user.email,
}),
};

return new MediatorResultSuccess(
userResponse,
ACTION_RESULT.SUCCESS_CREATE
);
}
}

export default CreateUserCommand;

Login User

Add a new login user action in the application layer.

funkallero command login user --folder user
src/application/user/login-user-command.ts
import {
ACTION_RESULT,
MediatorResultSuccess,
MediatorResultFailure,
injectService,
type ITokenService,
} from '@lindeneg/funkallero';
import SERVICE from '@/enums/service';
import BaseAction from '@/application/base-action';
import type { ILoginUserDto, ILoginUserResponse } from '@/contracts/login-user';

class LoginUserCommand extends BaseAction {
@injectService(SERVICE.TOKEN)
private readonly tokenService: ITokenService;

public async execute(dto: ILoginUserDto) {
const user = Array.from(this.dataContext.userRepository.values()).find(
(e) => e.email === dto.email
);

if (!user) {
return new MediatorResultFailure(ACTION_RESULT.ERROR_NOT_FOUND);
}

const isPasswordValid = await this.tokenService.comparePassword(
dto.password,
user.password
);

if (!isPasswordValid) {
return new MediatorResultFailure(ACTION_RESULT.ERROR_NOT_FOUND);
}

const loginResponse: ILoginUserResponse = {
token: await this.tokenService.createToken({
id: user.id,
email: user.email,
}),
};

return new MediatorResultSuccess(loginResponse);
}
}

export default LoginUserCommand;

Update Data Context

Hash password from new user. Inject and use tokenService.

src/services/data-context-service.ts
import { randomUUID } from 'crypto';
import {
injectService,
SingletonService,
type ILoggerService,
type IDataContextService,
type ITokenService,
} from '@lindeneg/funkallero';
import SERVICE from '@/enums/service';
import type User from '@/domain/user';
import type { ICreateUserDto } from '@/contracts/create-user';

class DataContextService
extends SingletonService
implements IDataContextService
{
@injectService(SERVICE.LOGGER)
private readonly logger: ILoggerService;

@injectService(SERVICE.TOKEN)
private readonly tokenService: ITokenService;

public readonly userRepository = new Map<User['id'], User>();

public async createUser(user: ICreateUserDto) {
const id = randomUUID();
const now = new Date();
const createdUser: User = {
...user,
id,
createdAt: now,
updatedAt: now,
password: await this.tokenService.hashPassword(user.password),
};
this.userRepository.set(id, createdUser);
return createdUser;
}
}

export default DataContextService;

Use Auth Policies

Add a new auth controller.

funkallero controller auth
src/api/auth-controller.ts
import {
controller,
httpGet,
httpPost,
body,
auth,
MediatorResultSuccess,
} from '@lindeneg/funkallero';
import BaseController from './base-controller';
import { loginUserSchema, type ILoginUserDto } from '@/contracts/login-user';

@controller('auth')
class AuthController extends BaseController {
@httpPost('/login')
public async loginUser(@body(loginUserSchema) dto: ILoginUserDto) {
return this.mediator.send('LoginUserCommand', dto);
}

@httpGet('/guard')
@auth('authenticated')
public async mustBeAuthenticated() {
// this will only be executed if the auth policy is satisfied
return new MediatorResultSuccess('you are authenticated');
}

@httpGet('/miles')
@auth('name-is-miles-davis')
public async mustBeMilesDavis() {
// this will only be executed if the auth policy is satisfied
return new MediatorResultSuccess('you are miles davis');
}
}

Next

Time to test it!