From a852e824518d32c3d324d01f6b62f06a3c797c9f Mon Sep 17 00:00:00 2001 From: Sergey Yarkov Date: Sat, 7 Aug 2021 16:11:06 +0300 Subject: [PATCH] added: AuthService --- app/Controllers/Http/Api/v1/AuthController.ts | 57 +++++----- app/Repositories/UserRepository.ts | 12 +++ app/Services/AuthService.ts | 100 ++++++++++++++++++ app/Services/UserService.ts | 18 +++- app/Validators/Auth/AuthValidator.ts | 13 +++ routes/apis/v1/users.ts | 8 +- 6 files changed, 176 insertions(+), 32 deletions(-) create mode 100644 app/Services/AuthService.ts create mode 100644 app/Validators/Auth/AuthValidator.ts diff --git a/app/Controllers/Http/Api/v1/AuthController.ts b/app/Controllers/Http/Api/v1/AuthController.ts index 2ba8314..bbab909 100644 --- a/app/Controllers/Http/Api/v1/AuthController.ts +++ b/app/Controllers/Http/Api/v1/AuthController.ts @@ -1,20 +1,24 @@ import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; -import Logger from '@ioc:Adonis/Core/Logger'; -import Hash from '@ioc:Adonis/Core/Hash'; -import { Exception } from '@adonisjs/core/build/standalone'; +import { Exception, inject, Ioc } from '@adonisjs/core/build/standalone'; +import AuthService from 'App/Services/AuthService'; + +/** + * Validators + */ +import AuthValidator from 'App/Validators/Auth/AuthValidator'; /** * Models */ -import User from 'App/Models/User'; +import BaseController from '../../BaseController'; -export default class AuthController { - private User: typeof User; +@inject() +export default class AuthController extends BaseController { + private authService: AuthService; - private guard: 'api'; - - constructor() { - this.User = User; + constructor(authService: AuthService) { + super(); + this.authService = authService; } /** @@ -22,23 +26,15 @@ export default class AuthController { * POST /login */ - public async login({ auth, request }: HttpContextContract) { - const login = request.input('login'); - const password = request.input('password'); - const user = await this.User.query().preload('roles').where('login', login).firstOrFail(); + public async login(ctx: HttpContextContract) { + const payload = await ctx.request.validate(AuthValidator); + const result = await this.authService.login(payload.login, payload.password, ctx.auth); - if (!(await Hash.verify(user.password, password))) { - throw new Exception('Invalid credentials.', 403, 'E_INVALID_CREDENTIALS'); + if (!result.success && result.error) { + throw new Exception(result.message, result.status, result.error.code); } - const token = await auth.use(this.guard).generate(user, { - expiresIn: '1d', - userRoles: user.roles.map(role => role.slug), - }); - - Logger.info(`A token for user with id: "${token.user.id}" was successfully generated.`); - - return token.toJSON(); + return this.sendResponse(ctx, result.data, result.message, result.status); } /** @@ -46,8 +42,15 @@ export default class AuthController { * POST /logout */ - public async logout({ response, auth }: HttpContextContract) { - await auth.use(this.guard).revoke(); - return response.noContent(); + public async logout(ctx: HttpContextContract) { + const result = await this.authService.logout(ctx.auth); + + if (!result.success && result.error) { + throw new Exception(result.message, result.status, result.error.code); + } + + return this.sendResponse(ctx, result.data, result.message, result.status); } } + +new Ioc().make(AuthController); diff --git a/app/Repositories/UserRepository.ts b/app/Repositories/UserRepository.ts index 633c6f1..6e0e925 100644 --- a/app/Repositories/UserRepository.ts +++ b/app/Repositories/UserRepository.ts @@ -21,6 +21,18 @@ export default class UserRepository { return data; } + /** + * Get user by column + * + * @param column Column name + * @param value Comparsion column + * @returns User or null + */ + public async getByColumn(column: string, value: string | number) { + const data = await this.User.query().preload('contacts').preload('roles').where(column, value).first(); + return data; + } + /** * Get users by role * diff --git a/app/Services/AuthService.ts b/app/Services/AuthService.ts new file mode 100644 index 0000000..5099df6 --- /dev/null +++ b/app/Services/AuthService.ts @@ -0,0 +1,100 @@ +import { inject, Ioc } from '@adonisjs/core/build/standalone'; +import Hash from '@ioc:Adonis/Core/Hash'; +import Logger from '@ioc:Adonis/Core/Logger'; + +/** + * Interfaces + */ +import { AuthContract } from '@ioc:Adonis/Addons/Auth'; +import IResponse from 'App/Datatypes/Interfaces/IResponse'; + +/** + * Reposirories + */ +import UserRepository from 'App/Repositories/UserRepository'; + +@inject() +export default class AuthService { + private userRepository: UserRepository; + + private guard: 'api'; + + constructor(userRepository: UserRepository) { + this.userRepository = userRepository; + } + + /** + * Login user + * + * @param login Login + * @param password Password + * @param auth AuthContact + * @returns Token data + */ + public async login(login: string, password: string, auth: AuthContract): Promise { + const user = await this.userRepository.getByColumn('login', login); + + if (!user) { + return { + success: false, + status: 404, + message: 'User not found.', + data: {}, + error: { + code: 'E_USER_NOT_FOUND', + }, + }; + } + + /** + * Invalid credentials while login + */ + if (!(await Hash.verify(user.password, password))) { + return { + success: false, + status: 403, + message: 'Invalid credentials.', + data: {}, + error: { + code: 'E_INVALID_CREDENTIALS', + }, + }; + } + + /** + * Create new token + */ + const token = await auth.use(this.guard).generate(user, { + expiresIn: '1d', + userRoles: user.roles.map(role => role.slug), + }); + + Logger.info(`A token for user with id: "${token.user.id}" was successfully generated.`); + + return { + success: true, + status: 200, + message: 'Login success.', + data: token.toJSON(), + }; + } + + /** + * Logout user + * + * @param auth AuthContract + * @returns Logout message + */ + public async logout(auth: AuthContract): Promise { + await auth.use(this.guard).revoke(); + + return { + success: true, + status: 200, + message: 'Logout success.', + data: {}, + }; + } +} + +new Ioc().make(AuthService); diff --git a/app/Services/UserService.ts b/app/Services/UserService.ts index 89bee47..f83d4f0 100644 --- a/app/Services/UserService.ts +++ b/app/Services/UserService.ts @@ -1,9 +1,25 @@ import { inject, Ioc } from '@adonisjs/core/build/standalone'; + +/** + * Interfaces + */ import IResponse from 'App/Datatypes/Interfaces/IResponse'; + +/** + * Models + */ import Role from 'App/Models/Role'; + +/** + * Repositories + */ import ContactRepository from 'App/Repositories/ContactRepository'; import RoleRepository from 'App/Repositories/RoleRepository'; import UserRepository from 'App/Repositories/UserRepository'; + +/** + * Validators + */ import CreateUserValidator from 'App/Validators/User/CreateUserValidator'; import UpdateUserValidator from 'App/Validators/User/UpdateUserValidator'; @@ -37,7 +53,7 @@ export default class UserService { message: 'User not found.', data: {}, error: { - code: 'USER_NOT_FOUND', + code: 'E_USER_NOT_FOUND', }, }; } diff --git a/app/Validators/Auth/AuthValidator.ts b/app/Validators/Auth/AuthValidator.ts new file mode 100644 index 0000000..fdd333a --- /dev/null +++ b/app/Validators/Auth/AuthValidator.ts @@ -0,0 +1,13 @@ +import { schema } from '@ioc:Adonis/Core/Validator'; +import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; + +export default class AuthValidator { + constructor(protected ctx: HttpContextContract) {} + + public schema = schema.create({ + login: schema.string(), + password: schema.string(), + }); + + public messages = {}; +} diff --git a/routes/apis/v1/users.ts b/routes/apis/v1/users.ts index e468147..1b25437 100644 --- a/routes/apis/v1/users.ts +++ b/routes/apis/v1/users.ts @@ -4,12 +4,12 @@ Route.group(() => { Route.get('/', 'Api/v1/UsersController.list').middleware('role:admin,teacher,student').as('users.list'); Route.post('/', 'Api/v1/UsersController.create').middleware('role:admin,student').as('users.create'); Route.get('/:id', 'Api/v1/UsersController.show').middleware('role:admin,teacher,student').as('users.show'); - Route.put('/:id', 'Api/v1/UsersController.update').middleware('role:admin,student,teacher').as('users.update'); - Route.delete('/:id', 'Api/v1/UsersController.delete').middleware('role:admin,student,teacher').as('users.delete'); + Route.put('/:id', 'Api/v1/UsersController.update').middleware('role:admin').as('users.update'); + Route.delete('/:id', 'Api/v1/UsersController.delete').middleware('role:admin').as('users.delete'); Route.post('/:id/attach-roles', 'Api/v1/UsersController.attachRoles') - .middleware('role:admin,student,teacher') + .middleware('role:admin') .as('users.attach-role'); Route.delete('/:id/detach-roles', 'Api/v1/UsersController.detachRoles') - .middleware('role:admin,student,teacher') + .middleware('role:admin') .as('users.detach-role'); }).prefix('users');