Fix JWT authentication, add custom user service
This commit is contained in:
parent
ee2d7b4838
commit
cfc3d48a7b
4 changed files with 87 additions and 30 deletions
|
@ -11,7 +11,7 @@ import path from 'path';
|
||||||
import {MySequence} from './sequence';
|
import {MySequence} from './sequence';
|
||||||
import {AuthenticationComponent} from '@loopback/authentication';
|
import {AuthenticationComponent} from '@loopback/authentication';
|
||||||
import {JWTAuthenticationComponent, UserServiceBindings} from '@loopback/authentication-jwt';
|
import {JWTAuthenticationComponent, UserServiceBindings} from '@loopback/authentication-jwt';
|
||||||
import {DatabaseDataSource} from './datasources';
|
import {SzakdolgozatUserService} from './services';
|
||||||
|
|
||||||
export {ApplicationConfig};
|
export {ApplicationConfig};
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ export class SzakdolgozatBackendApplication extends BootMixin(
|
||||||
// Authentication
|
// Authentication
|
||||||
this.component(AuthenticationComponent);
|
this.component(AuthenticationComponent);
|
||||||
this.component(JWTAuthenticationComponent);
|
this.component(JWTAuthenticationComponent);
|
||||||
this.dataSource(DatabaseDataSource, UserServiceBindings.DATASOURCE_NAME)
|
this.service(SzakdolgozatUserService, UserServiceBindings.USER_SERVICE);
|
||||||
|
|
||||||
this.projectRoot = __dirname;
|
this.projectRoot = __dirname;
|
||||||
// Customize @loopback/boot Booter Conventions here
|
// Customize @loopback/boot Booter Conventions here
|
||||||
|
|
|
@ -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 {del, get, getModelSchemaRef, param, patch, post, put, requestBody, response,} from '@loopback/rest';
|
||||||
import {User} from '../models';
|
import {User} from '../models';
|
||||||
import {UserRepository} from '../repositories';
|
import {UserRepository} from '../repositories';
|
||||||
import {
|
import {
|
||||||
Credentials,
|
|
||||||
MyUserService,
|
|
||||||
TokenServiceBindings,
|
TokenServiceBindings,
|
||||||
UserServiceBindings
|
UserServiceBindings
|
||||||
} from '@loopback/authentication-jwt';
|
} from '@loopback/authentication-jwt';
|
||||||
import {inject, MetadataInspector} from '@loopback/core';
|
import {inject} from '@loopback/core';
|
||||||
import {TokenService} from '@loopback/authentication';
|
import {TokenService} from '@loopback/authentication';
|
||||||
import {SecurityBindings, UserProfile} from '@loopback/security';
|
import {SecurityBindings, UserProfile} from '@loopback/security';
|
||||||
import {genSalt, hash} from 'bcryptjs';
|
import {genSalt, hash} from 'bcryptjs';
|
||||||
import _ from 'lodash';
|
import {SzakdolgozatUserService} from '../services';
|
||||||
import {UserRepository as UserRepo} from '@loopback/authentication-jwt';
|
|
||||||
|
|
||||||
export class UserController {
|
export class UserController {
|
||||||
constructor(
|
constructor(
|
||||||
@inject(TokenServiceBindings.TOKEN_SERVICE)
|
@inject(TokenServiceBindings.TOKEN_SERVICE)
|
||||||
public jwtService: TokenService,
|
public jwtService: TokenService,
|
||||||
@inject(UserServiceBindings.USER_SERVICE)
|
@inject(UserServiceBindings.USER_SERVICE)
|
||||||
public userService: MyUserService,
|
public userService: SzakdolgozatUserService,
|
||||||
@inject(SecurityBindings.USER, {optional: true})
|
@inject(SecurityBindings.USER, {optional: true})
|
||||||
public user: UserProfile,
|
public user: UserProfile,
|
||||||
@repository(UserRepo)
|
|
||||||
public userRepository : UserRepo,
|
|
||||||
@repository(UserRepository)
|
@repository(UserRepository)
|
||||||
public userDataRepository : UserRepository,
|
public userRepository : 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");
|
|
||||||
}
|
|
||||||
|
|
||||||
@post('/users')
|
@post('/users')
|
||||||
@response(200, {
|
@response(200, {
|
||||||
|
@ -62,7 +49,7 @@ export class UserController {
|
||||||
password: password,
|
password: password,
|
||||||
role: 'student'
|
role: 'student'
|
||||||
} as User;
|
} as User;
|
||||||
return this.userDataRepository.create(user);
|
return this.userRepository.create(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@post('/users/login', {
|
@post('/users/login', {
|
||||||
|
@ -92,7 +79,7 @@ export class UserController {
|
||||||
'application/json': {
|
'application/json': {
|
||||||
schema: getModelSchemaRef(User, {exclude: ['id', 'role', 'name']})
|
schema: getModelSchemaRef(User, {exclude: ['id', 'role', 'name']})
|
||||||
},
|
},
|
||||||
}}) credentials: Credentials,
|
}}) credentials: Pick<User, 'email' | 'password'>,
|
||||||
): Promise<{token: string}> {
|
): Promise<{token: string}> {
|
||||||
// ensure the user exists, and the password is correct
|
// ensure the user exists, and the password is correct
|
||||||
const user = await this.userService.verifyCredentials(credentials);
|
const user = await this.userService.verifyCredentials(credentials);
|
||||||
|
@ -112,7 +99,7 @@ export class UserController {
|
||||||
async count(
|
async count(
|
||||||
@param.where(User) where?: Where<User>,
|
@param.where(User) where?: Where<User>,
|
||||||
): Promise<Count> {
|
): Promise<Count> {
|
||||||
return this.userDataRepository.count(where);
|
return this.userRepository.count(where);
|
||||||
}
|
}
|
||||||
|
|
||||||
@get('/users')
|
@get('/users')
|
||||||
|
@ -130,7 +117,7 @@ export class UserController {
|
||||||
async find(
|
async find(
|
||||||
@param.filter(User) filter?: Filter<User>,
|
@param.filter(User) filter?: Filter<User>,
|
||||||
): Promise<User[]> {
|
): Promise<User[]> {
|
||||||
return this.userDataRepository.find(filter);
|
return this.userRepository.find(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@patch('/users')
|
@patch('/users')
|
||||||
|
@ -149,7 +136,7 @@ export class UserController {
|
||||||
user: User,
|
user: User,
|
||||||
@param.where(User) where?: Where<User>,
|
@param.where(User) where?: Where<User>,
|
||||||
): Promise<Count> {
|
): Promise<Count> {
|
||||||
return this.userDataRepository.updateAll(user, where);
|
return this.userRepository.updateAll(user, where);
|
||||||
}
|
}
|
||||||
|
|
||||||
@get('/users/{id}')
|
@get('/users/{id}')
|
||||||
|
@ -165,7 +152,7 @@ export class UserController {
|
||||||
@param.path.number('id') id: number,
|
@param.path.number('id') id: number,
|
||||||
@param.filter(User, {exclude: 'where'}) filter?: FilterExcludingWhere<User>
|
@param.filter(User, {exclude: 'where'}) filter?: FilterExcludingWhere<User>
|
||||||
): Promise<User> {
|
): Promise<User> {
|
||||||
return this.userDataRepository.findById(id, filter);
|
return this.userRepository.findById(id, filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@patch('/users/{id}')
|
@patch('/users/{id}')
|
||||||
|
@ -183,7 +170,7 @@ export class UserController {
|
||||||
})
|
})
|
||||||
user: User,
|
user: User,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await this.userDataRepository.updateById(id, user);
|
await this.userRepository.updateById(id, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@put('/users/{id}')
|
@put('/users/{id}')
|
||||||
|
@ -194,7 +181,7 @@ export class UserController {
|
||||||
@param.path.number('id') id: number,
|
@param.path.number('id') id: number,
|
||||||
@requestBody() user: User,
|
@requestBody() user: User,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await this.userDataRepository.replaceById(id, user);
|
await this.userRepository.replaceById(id, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@del('/users/{id}')
|
@del('/users/{id}')
|
||||||
|
@ -202,6 +189,6 @@ export class UserController {
|
||||||
description: 'User DELETE success',
|
description: 'User DELETE success',
|
||||||
})
|
})
|
||||||
async deleteById(@param.path.number('id') id: number): Promise<void> {
|
async deleteById(@param.path.number('id') id: number): Promise<void> {
|
||||||
await this.userDataRepository.deleteById(id);
|
await this.userRepository.deleteById(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
1
backend/src/services/index.ts
Normal file
1
backend/src/services/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from './user.service';
|
69
backend/src/services/user.service.ts
Normal file
69
backend/src/services/user.service.ts
Normal file
|
@ -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<User, Credentials> {
|
||||||
|
constructor(
|
||||||
|
@repository(UserRepository) public userRepository: UserRepository,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
async verifyCredentials(credentials: Credentials): Promise<User> {
|
||||||
|
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<User & UserWithRelations> {
|
||||||
|
const userNotfound = 'invalid user';
|
||||||
|
const foundUser = await this.userRepository.findById(id);
|
||||||
|
|
||||||
|
if (!foundUser) {
|
||||||
|
throw new HttpErrors.Unauthorized(userNotfound);
|
||||||
|
}
|
||||||
|
return foundUser;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue