Skip to content

Commit

Permalink
Merge pull request #16 from victor-0x29a/patch/auth-service-for-abstr…
Browse files Browse the repository at this point in the history
…action

Patch: auth service
  • Loading branch information
victor-0x29a authored Sep 25, 2024
2 parents 28ea9cc + 7d587cc commit 17a4834
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 16 deletions.
5 changes: 3 additions & 2 deletions src/controllers/user.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ import { LogService } from "../services/log.service";


class UserController extends BaseController {
private Service = new UserService(UserModel)
private LogService = new LogService()
public readonly router = Router()

constructor() {
Expand All @@ -24,6 +22,9 @@ class UserController extends BaseController {
}
}

private Service = new UserService(UserModel, this.getApplicationSecret())
private LogService = new LogService()

private loadRoutes() {
this.router.get('/', Guard, this.getAll)
this.router.get('/:id', Guard, this.getById)
Expand Down
73 changes: 73 additions & 0 deletions src/services/auth.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { User } from "../models";
import { Auth } from "./auth.service";

const userData = {
id: 1,
name: 'foo',
username: 'bar',
password: '$----secret----$'
} as unknown as User

describe('should success in all flow methods', () => {
const appSecret = '--key--'

const service = new Auth(userData, appSecret)

test('should get the sign payload', () => {
const payloadExpected = {
id: userData.id,
username: userData.username
}

expect(payloadExpected).toEqual(service.tokenPayload)
})

test('should get the token options', () => {
const optionsExpected = {
expiresIn: '20min'
}

expect(optionsExpected).toEqual(service.tokenOptions)
})

test('should sign a token', () => {
const tokenSigned = service.signToken()

const tokenParts = tokenSigned.split('.').length

expect(typeof tokenSigned).toEqual('string')
expect(tokenParts).toBe(3)
})
})

describe('should test all validation methods', () => {
const generateService = (appSecret: undefined | number | object | null | string) => new Auth(userData, appSecret as unknown as string)

const validationErrorMessage = 'The application secret must be provided.'

test('should not sign a token when havent app secret', () => {
const service = generateService(undefined)

expect(() => service.signToken()).toThrow(validationErrorMessage)

const serviceWithNullSecret = generateService(null)

expect(() => serviceWithNullSecret.signToken()).toThrow(validationErrorMessage)

const serviceWithEmptyString = generateService('')

expect(() => serviceWithEmptyString.signToken()).toThrow(validationErrorMessage)
})

test('shoult not sign a token when the app secret is a number', () => {
const service = generateService(9)

expect(() => service.signToken()).toThrow(validationErrorMessage)
})

test('should not sign a token when the app secret is an object', () => {
const service = generateService({})

expect(() => service.signToken()).toThrow(validationErrorMessage)
})
})
37 changes: 37 additions & 0 deletions src/services/auth.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import jwt from 'jsonwebtoken'
import type { User } from '../models'
import { InternalValidation } from '../web/errors'

class Auth {
constructor (private readonly userData: User, private readonly applicationSecret: string) {}

get tokenPayload () {
return {
id: this.userData.id,
username: this.userData.username
}
}

get tokenOptions () {
return {
expiresIn: '20min'
}
}

private validate(): void {
const haventApplicationSecret = !this.applicationSecret
const applicationSecretIsString = typeof this.applicationSecret === 'string'

if (haventApplicationSecret || !applicationSecretIsString) {
throw new InternalValidation('The application secret must be provided.')
}
}

public signToken () {
this.validate()

return jwt.sign(this.tokenPayload, this.applicationSecret, this.tokenOptions)
}
}

export { Auth }
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ class Environment implements EnvironmentService {
getPort(): number {
return Number(this.environmentVars.PORT)
}

getApplicationSecret(): string {
return String(this.environmentVars.JWT_SECRET)
}
}

export {
Expand Down
2 changes: 1 addition & 1 deletion src/services/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { Environment as EnvironmentService } from "./evironment.service";
export { Environment as EnvironmentService } from "./environment.service";
2 changes: 1 addition & 1 deletion src/services/user.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ let UserModel = {
findOne: jest.fn()
} as unknown as ModelCtor<Model<any, any>>

const service = new UserService(UserModel)
const service = new UserService(UserModel, 'key')

const hash = '$2b$10$1Q6Zz1'

Expand Down
21 changes: 10 additions & 11 deletions src/services/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,27 @@ import { User, UserModel } from "../models";
import { searchEntity } from "../utils/searchEntity";
import * as bcrypt from 'bcrypt'
import { LegendHttpError } from "../web/errors";
import jwt from 'jsonwebtoken'
import { Auth as AuthService } from "./auth.service";


class UserService {
constructor(private readonly userModel: typeof UserModel) {}
constructor(private readonly userModel: typeof UserModel, private readonly applicationSecret: string = '') {}

async signIn(signInDto: SignInDto): Promise<string> {
const user = await searchEntity<User>(this.userModel, { username: signInDto.username }, false, false)

if (user === null) {
throw new LegendHttpError(401, 'User or password invalid.')
}

const isPasswordMatch = await bcrypt.compare(signInDto.password, user.password)
const isPasswordMatch = await bcrypt.compare(
signInDto.password,
user?.password || ''
)

if (!isPasswordMatch) {
if ((user === null) || !isPasswordMatch) {
throw new LegendHttpError(401, 'User or password invalid.')
}

const token = await jwt.sign({ id: user.id, username: user.username }, process.env.JWT_SECRET, {
expiresIn: '20min'
})
const auth = new AuthService(user, this.applicationSecret)

const token = auth.signToken()

return token
}
Expand Down
8 changes: 7 additions & 1 deletion src/web/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,10 @@ class LegendHttpError extends Error {
}
}

export { LegendHttpError }
class InternalValidation extends Error {
constructor(public message: string) {
super(message)
}
}

export { LegendHttpError, InternalValidation }

0 comments on commit 17a4834

Please sign in to comment.