From cfc3d48a7b3de93edbd4c17ae50ee45e70be0fec Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Tue, 16 Nov 2021 22:28:56 +0100 Subject: [PATCH] Fix JWT authentication, add custom user service --- backend/src/application.ts | 4 +- backend/src/controllers/user.controller.ts | 43 +++++--------- backend/src/services/index.ts | 1 + backend/src/services/user.service.ts | 69 ++++++++++++++++++++++ 4 files changed, 87 insertions(+), 30 deletions(-) create mode 100644 backend/src/services/index.ts create mode 100644 backend/src/services/user.service.ts diff --git a/backend/src/application.ts b/backend/src/application.ts index 3aab82f..4cfb472 100644 --- a/backend/src/application.ts +++ b/backend/src/application.ts @@ -11,7 +11,7 @@ import path from 'path'; import {MySequence} from './sequence'; import {AuthenticationComponent} from '@loopback/authentication'; import {JWTAuthenticationComponent, UserServiceBindings} from '@loopback/authentication-jwt'; -import {DatabaseDataSource} from './datasources'; +import {SzakdolgozatUserService} from './services'; export {ApplicationConfig}; @@ -36,7 +36,7 @@ export class SzakdolgozatBackendApplication extends BootMixin( // Authentication this.component(AuthenticationComponent); this.component(JWTAuthenticationComponent); - this.dataSource(DatabaseDataSource, UserServiceBindings.DATASOURCE_NAME) + this.service(SzakdolgozatUserService, UserServiceBindings.USER_SERVICE); this.projectRoot = __dirname; // Customize @loopback/boot Booter Conventions here diff --git a/backend/src/controllers/user.controller.ts b/backend/src/controllers/user.controller.ts index 0c5c8d1..43fd6b0 100644 --- a/backend/src/controllers/user.controller.ts +++ b/backend/src/controllers/user.controller.ts @@ -1,41 +1,28 @@ -import {Count, CountSchema, Filter, FilterExcludingWhere, MODEL_PROPERTIES_KEY, repository, Where,} from '@loopback/repository'; +import {Count, CountSchema, Filter, FilterExcludingWhere, repository, Where,} from '@loopback/repository'; import {del, get, getModelSchemaRef, param, patch, post, put, requestBody, response,} from '@loopback/rest'; import {User} from '../models'; import {UserRepository} from '../repositories'; import { - Credentials, - MyUserService, TokenServiceBindings, UserServiceBindings } from '@loopback/authentication-jwt'; -import {inject, MetadataInspector} from '@loopback/core'; +import {inject} from '@loopback/core'; import {TokenService} from '@loopback/authentication'; import {SecurityBindings, UserProfile} from '@loopback/security'; import {genSalt, hash} from 'bcryptjs'; -import _ from 'lodash'; -import {UserRepository as UserRepo} from '@loopback/authentication-jwt'; +import {SzakdolgozatUserService} from '../services'; export class UserController { constructor( @inject(TokenServiceBindings.TOKEN_SERVICE) public jwtService: TokenService, @inject(UserServiceBindings.USER_SERVICE) - public userService: MyUserService, + public userService: SzakdolgozatUserService, @inject(SecurityBindings.USER, {optional: true}) public user: UserProfile, - @repository(UserRepo) - public userRepository : UserRepo, @repository(UserRepository) - public userDataRepository : UserRepository, - ) {} - - static includeToExclude(values: (keyof User)[]): (keyof User)[] { - const metadata = MetadataInspector.getAllPropertyMetadata(MODEL_PROPERTIES_KEY, User); - if (metadata) { - return _.without(Object.keys(metadata), ...values) as (keyof User)[]; - } - else throw new Error("No property metadata found"); - } + public userRepository : UserRepository, + ) { } @post('/users') @response(200, { @@ -62,7 +49,7 @@ export class UserController { password: password, role: 'student' } as User; - return this.userDataRepository.create(user); + return this.userRepository.create(user); } @post('/users/login', { @@ -92,7 +79,7 @@ export class UserController { 'application/json': { schema: getModelSchemaRef(User, {exclude: ['id', 'role', 'name']}) }, - }}) credentials: Credentials, + }}) credentials: Pick, ): Promise<{token: string}> { // ensure the user exists, and the password is correct const user = await this.userService.verifyCredentials(credentials); @@ -112,7 +99,7 @@ export class UserController { async count( @param.where(User) where?: Where, ): Promise { - return this.userDataRepository.count(where); + return this.userRepository.count(where); } @get('/users') @@ -130,7 +117,7 @@ export class UserController { async find( @param.filter(User) filter?: Filter, ): Promise { - return this.userDataRepository.find(filter); + return this.userRepository.find(filter); } @patch('/users') @@ -149,7 +136,7 @@ export class UserController { user: User, @param.where(User) where?: Where, ): Promise { - return this.userDataRepository.updateAll(user, where); + return this.userRepository.updateAll(user, where); } @get('/users/{id}') @@ -165,7 +152,7 @@ export class UserController { @param.path.number('id') id: number, @param.filter(User, {exclude: 'where'}) filter?: FilterExcludingWhere ): Promise { - return this.userDataRepository.findById(id, filter); + return this.userRepository.findById(id, filter); } @patch('/users/{id}') @@ -183,7 +170,7 @@ export class UserController { }) user: User, ): Promise { - await this.userDataRepository.updateById(id, user); + await this.userRepository.updateById(id, user); } @put('/users/{id}') @@ -194,7 +181,7 @@ export class UserController { @param.path.number('id') id: number, @requestBody() user: User, ): Promise { - await this.userDataRepository.replaceById(id, user); + await this.userRepository.replaceById(id, user); } @del('/users/{id}') @@ -202,6 +189,6 @@ export class UserController { description: 'User DELETE success', }) async deleteById(@param.path.number('id') id: number): Promise { - await this.userDataRepository.deleteById(id); + await this.userRepository.deleteById(id); } } diff --git a/backend/src/services/index.ts b/backend/src/services/index.ts new file mode 100644 index 0000000..e17ee5c --- /dev/null +++ b/backend/src/services/index.ts @@ -0,0 +1 @@ +export * from './user.service'; diff --git a/backend/src/services/user.service.ts b/backend/src/services/user.service.ts new file mode 100644 index 0000000..de276a3 --- /dev/null +++ b/backend/src/services/user.service.ts @@ -0,0 +1,69 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/authentication-jwt +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {UserService} from '@loopback/authentication'; +import {repository} from '@loopback/repository'; +import {HttpErrors} from '@loopback/rest'; +import {securityId, UserProfile} from '@loopback/security'; +import {compare} from 'bcryptjs'; +import {User, UserWithRelations} from '../models'; +import {UserRepository} from '../repositories'; + +/** + * A pre-defined type for user credentials. It assumes a user logs in + * using the email and password. You can modify it if your app has different credential fields + */ +export type Credentials = { + email: string; + password: string; +}; + +export class SzakdolgozatUserService implements UserService { + constructor( + @repository(UserRepository) public userRepository: UserRepository, + ) { } + + async verifyCredentials(credentials: Credentials): Promise { + const invalidCredentialsError = 'Invalid email or password.'; + + const foundUser = await this.userRepository.findOne({ + where: {email: credentials.email}, + }); + if (!foundUser) { + throw new HttpErrors.Unauthorized(invalidCredentialsError); + } + + const passwordMatched = await compare( + credentials.password, + foundUser.password, + ); + + if (!passwordMatched) { + throw new HttpErrors.Unauthorized(invalidCredentialsError); + } + + return foundUser; + } + + convertToUserProfile({email, id, name}: User): UserProfile { + return { + [securityId]: id!.toString(), + name, + id, + email, + }; + } + + //function to find user by id + async findUserById(id: number): Promise { + const userNotfound = 'invalid user'; + const foundUser = await this.userRepository.findById(id); + + if (!foundUser) { + throw new HttpErrors.Unauthorized(userNotfound); + } + return foundUser; + } +}