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!