Skip to content

Commit a5cff2b

Browse files
committed
feature: add authentication guard
1 parent a30f671 commit a5cff2b

File tree

15 files changed

+103
-85
lines changed

15 files changed

+103
-85
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { InfrastructureInjectionList } from '@infrastructure/InfrastructureInjectionList'
2+
import { IAuthenticationClient } from '@modules/shared/infrastructure/IAuthenticationClient'
3+
import { CanActivate, ExecutionContext, Inject, Injectable } from '@nestjs/common'
4+
5+
@Injectable()
6+
export class AuthenticationGuard implements CanActivate {
7+
constructor(
8+
@Inject(InfrastructureInjectionList.AUTHENTICATION_CLIENT.provide)
9+
private readonly authenticationClient: IAuthenticationClient
10+
) {}
11+
12+
async canActivate(context: ExecutionContext): Promise<boolean> {
13+
const req = context.switchToHttp().getRequest()
14+
15+
return await this.authenticationClient.validateLogin(req.headers.authorization)
16+
}
17+
}

src/app.module.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import { AuthenticationModule } from './modules/authentication/authentication.module'
1+
import { InfrastructureModule } from '@infrastructure/infrastructure.module'
2+
import { UsersModule } from '@modules/users/users.module'
23
import { Module } from '@nestjs/common'
4+
import { AuthenticationModule } from './modules/authentication/authentication.module'
35

46
@Module({
5-
imports: [AuthenticationModule],
7+
imports: [AuthenticationModule, UsersModule, InfrastructureModule],
68
controllers: [],
79
providers: []
810
})
Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import { EnumCountry } from '@modules/authentication/presentation/dtos/enums/EnumCountry'
22
import { AccountDto } from '@modules/shared/presentation/dto/AccountDto'
3-
import { Injectable } from '@nestjs/common'
4-
import { clientErrorMessages, ClientResult, IAuthenticationClient } from './IAuthenticationClient'
3+
import { BadRequestException, Injectable } from '@nestjs/common'
4+
import * as crypto from 'node:crypto'
5+
import { IAuthenticationClient } from '../../modules/shared/infrastructure/IAuthenticationClient'
6+
import { ClientErrorMessages } from './ClientErrorMessages'
57

68
//Fake client adapter simulating requests to a third party authentication API
79

810
export const account1: AccountDto = {
911
id: 'bd10c4e7-6385-41a6-a9d1-90c0ee80db0d',
1012
name: 'Name One',
11-
email: 'one@mail.com',
13+
email: 'one@mail.com',
1214
age: 11,
1315
password: 'user1',
1416
address: {
@@ -20,55 +22,55 @@ export const account1: AccountDto = {
2022
export const account2: AccountDto = {
2123
id: 'e77b7a0a-b26e-438d-8bff-d6160c98fb4a',
2224
name: 'Name Two',
23-
email: 'two@mail.com',
25+
email: 'two@mail.com',
2426
password: 'user2',
2527
age: 22,
2628
address: {
27-
country: EnumCountry.US
29+
country: EnumCountry.US
2830
}
2931
}
3032

3133
const accounts: AccountDto[] = [account1, account2]
3234

33-
35+
const loginTokens: string[] = []
3436
@Injectable()
3537
export class AuthenticationClient implements IAuthenticationClient {
36-
async getAccountByEmail(email: string): Promise<AccountDto> {
37-
return accounts.find((account) => account.email === email)
38-
}
39-
40-
async createAccount(account: AccountDto): Promise<ClientResult<AccountDto>> {
38+
async createAccount(account: AccountDto): Promise<AccountDto> {
4139
const accountExists = await this.getAccountByEmail(account.email)
4240
if (accountExists) {
43-
return {status: 400, data: clientErrorMessages.ACCOUNT_ALREADY_EXISTS}
41+
throw new BadRequestException(ClientErrorMessages.ACCOUNT_ALREADY_EXISTS)
4442
}
45-
accounts.push(account)
46-
return {status: 200, data: account}
43+
accounts.push(account)
44+
return account
4745
}
4846

49-
async login(email: string, password: string): Promise<ClientResult<string>> {
47+
async login(email: string, password: string): Promise<string> {
5048
const account = await this.getAccountByEmail(email)
51-
if (!account) {
52-
return {status: 400, data: clientErrorMessages.ACCOUNT_NOT_FOUND}
49+
if (account && account.password === password) {
50+
const loginToken = crypto.randomUUID() //Generate fake token for login
51+
loginTokens.push(loginToken)
52+
return loginToken
5353
}
54-
if(account.password !== password){
55-
return {status: 400, data: clientErrorMessages.INVALID_CREDENTIALS}
56-
}
57-
return {status: 200, data: 'SuccessLoginString'}
54+
throw new BadRequestException(ClientErrorMessages.INVALID_CREDENTIALS)
5855
}
5956

60-
async deleteAccountByEmail(email: string): Promise<boolean> {
61-
const accountExists = await this.getAccountByEmail(email)
62-
if(!accountExists){
63-
return false
64-
}
65-
66-
accounts.splice(accounts.indexOf(accountExists), 1)
67-
return true
57+
async validateLogin(loginToken: string): Promise<boolean> {
58+
const isLoggedIn = loginTokens.find((token) => token === loginToken)
59+
60+
if (isLoggedIn) return true
61+
62+
return false
6863
}
6964

70-
// TODO replace Account for a DTO
7165
async getAllAccounts(): Promise<AccountDto[]> {
7266
return Promise.resolve(accounts)
7367
}
68+
69+
async getAccountByEmail(email: string): Promise<AccountDto> {
70+
return accounts.find((account) => account.email === email)
71+
}
72+
73+
async getAccountById(id: string): Promise<AccountDto> {
74+
return accounts.find((account) => account.id === id)
75+
}
7476
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export enum ClientErrorMessages {
2+
ACCOUNT_NOT_FOUND = 'An account with the given email was not found.',
3+
ACCOUNT_ALREADY_EXISTS = 'An account with the given email already exists.',
4+
INVALID_CREDENTIALS = 'Invalid email or password.',
5+
}

src/infrastructure/clients/IAuthenticationClient.ts

Lines changed: 0 additions & 20 deletions
This file was deleted.
Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { InfrastructureInjectionList } from '@infrastructure/InfrastructureInjectionList'
2+
import { AccountDto } from '@modules/shared/presentation/dto/AccountDto'
23
import { BadRequestException, Inject, Injectable, InternalServerErrorException } from '@nestjs/common'
3-
import { IAuthenticationClient } from '../../../infrastructure/clients/IAuthenticationClient'
4+
import { IAuthenticationClient } from '../../shared/infrastructure/IAuthenticationClient'
45
import { Account } from '../domain/entities/Account'
56
import { AuthenticationErrorMessages } from '../domain/errors/AuthenticationErrorMessages'
6-
import { ItemAlreadyExistsError } from '../domain/errors/itemAlreadyExists.error'
77
import { CreateAccountDto } from '../presentation/dtos/CreateAccountDto'
88
import { LoginDto } from '../presentation/dtos/LoginDto'
99
import { IAuthenticationService } from './IAuthenticationService'
@@ -15,15 +15,13 @@ export class AuthenticationService implements IAuthenticationService {
1515
private readonly authenticationClient: IAuthenticationClient
1616
) {}
1717

18-
async createAccount(body: CreateAccountDto): Promise<Account> {
18+
async createAccount(body: CreateAccountDto): Promise<AccountDto> {
1919
this.validateZipCode(body)
2020
const newAccount = await this.authenticationClient.createAccount(new Account(body))
21-
if (newAccount.status === 200) return newAccount.data as Account
21+
22+
if (newAccount) return newAccount
2223

23-
if (newAccount?.status === 400) {
24-
throw new BadRequestException(new ItemAlreadyExistsError('Account', 'Email'))
25-
}
26-
throw new InternalServerErrorException(AuthenticationErrorMessages.AUTHENTICATION_CLIENT_ERROR)
24+
throw new InternalServerErrorException(AuthenticationErrorMessages.SIGNUP_ERROR)
2725
}
2826

2927
//Useless business method created to simulate Throw - Reject on tests (authentication.service.spec.ts)
@@ -42,13 +40,8 @@ export class AuthenticationService implements IAuthenticationService {
4240
async login(body: LoginDto): Promise<string> {
4341
const login = await this.authenticationClient.login(body.email, body.password)
4442

45-
if (login?.status === 200) {
46-
return login.data
47-
}
48-
49-
if (login?.status === 400) {
50-
throw new BadRequestException(AuthenticationErrorMessages.INVALID_CREDENTIALS)
51-
}
52-
throw new InternalServerErrorException(AuthenticationErrorMessages.AUTHENTICATION_CLIENT_ERROR)
43+
if (login) return login
44+
45+
throw new InternalServerErrorException(AuthenticationErrorMessages.LOGIN_ERROR)
5346
}
5447
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { Account } from '../domain/entities/Account'
1+
import { AccountDto } from '@modules/shared/presentation/dto/AccountDto'
22
import { CreateAccountDto } from '../presentation/dtos/CreateAccountDto'
33
import { LoginDto } from '../presentation/dtos/LoginDto'
44
export interface IAuthenticationService {
5-
createAccount(body: CreateAccountDto): Promise<Account>
5+
createAccount(body: CreateAccountDto): Promise<AccountDto>
66
login(body: LoginDto): Promise<string>
77
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export enum AuthenticationErrorMessages {
22
INVALID_CREDENTIALS = 'Invalid email or password.',
33
INVALID_ZIPCODE = 'Invalid ZipCode',
4-
AUTHENTICATION_CLIENT_ERROR = 'Authentication Client Error'
4+
SIGNUP_ERROR = 'There was an unexpected error when trying to create your account. Please try again later.',
5+
LOGIN_ERROR = 'There was an unexpected error when trying to authenticate. Please try again later.'
56
}

src/modules/authentication/presentation/authentication.http

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,3 @@ content-type: application/json
2121
"email": "email@email.com",
2222
"password": "12345"
2323
}
24-
25-
###
26-
GET http://localhost:3003/getAllAccounts
27-
content-type: application/json
28-
29-
{}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { AccountDto } from '@modules/shared/presentation/dto/AccountDto'
2+
3+
export interface ClientResult<T> {
4+
status: number
5+
data: T | string
6+
}
7+
8+
export interface IAuthenticationClient {
9+
createAccount(account: AccountDto): Promise<AccountDto>
10+
login(email: string, password: string): Promise<string>
11+
validateLogin(loginToken: string): Promise<boolean>
12+
getAllAccounts(): Promise<AccountDto[]>
13+
getAccountByEmail(email: string): Promise<AccountDto>
14+
getAccountById(id: string): Promise<AccountDto>
15+
}

0 commit comments

Comments
 (0)