Implement login, find user, update user (partly)

This commit is contained in:
Norbi Peti 2022-04-21 01:03:34 +02:00
parent 099b40e77a
commit 65332d4947
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
6 changed files with 126 additions and 167 deletions

View file

@ -1,4 +1,4 @@
import { arg, GraphQLBindings, mutation, query, resolver, ResolverData } from '@loopback/graphql'; import { arg, authorized, GraphQLBindings, Int, mutation, query, resolver, ResolverData } from '@loopback/graphql';
import { User } from '../models'; import { User } from '../models';
import { repository } from '@loopback/repository'; import { repository } from '@loopback/repository';
import { UserRepository } from '../repositories'; import { UserRepository } from '../repositories';
@ -10,6 +10,8 @@ import { SecurityBindings, UserProfile } from '@loopback/security';
import { genSalt, hash } from 'bcryptjs'; import { genSalt, hash } from 'bcryptjs';
import { UserRegisterInput } from '../graphql-types/input/user-register.input'; import { UserRegisterInput } from '../graphql-types/input/user-register.input';
import { validated } from '../helpers'; import { validated } from '../helpers';
import { LoginResult } from '../graphql-types/user';
import { UserUpdateInput } from '../graphql-types/input/user-update.input';
@resolver(of => User) @resolver(of => User)
export class UserResolver { export class UserResolver {
@ -39,20 +41,10 @@ export class UserResolver {
return (await this.userRepository.find())[0]; return (await this.userRepository.find())[0];
} }
/*@mutation(returns => LoginResult) @mutation(returns => LoginResult)
async login( async login(@arg('email') email: string, @arg('password') password: string): Promise<LoginResult> {
@requestBody({
description: 'The input of login function',
required: true,
content: {
'application/json': {
schema: getModelSchemaRef(User, {exclude: ['id', 'isAdmin', 'name']})
},
}
}) credentials: Pick<User, 'email' | 'password'>,
): Promise<{ token: string, user: Omit<User, 'id' | 'password'> }> {
// 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({email, password});
// convert a User object into a UserProfile object (reduced set of properties) // convert a User object into a UserProfile object (reduced set of properties)
const userProfile = this.userService.convertToUserProfile(user); const userProfile = this.userService.convertToUserProfile(user);
@ -61,16 +53,11 @@ export class UserResolver {
return {token, user}; return {token, user};
} }
@post('/users/logout', { @authorized()
responses: { @mutation(returns => Boolean)
'204': { async logout(@inject(GraphQLBindings.RESOLVER_DATA) request: any): Promise<boolean> {
description: 'Logged out', console.log('request:', request); //TODO
}, const split = request.headers.get('Authorization')?.split(' ');
},
})
@authenticate('jwt')
async logout(@inject(RestBindings.Http.REQUEST) request: Request): Promise<void> {
const split = request.headers.authorization?.split(' ');
if (split && split.length > 1) { if (split && split.length > 1) {
if (this.jwtService.revokeToken) { if (this.jwtService.revokeToken) {
await this.jwtService.revokeToken(split[1]); await this.jwtService.revokeToken(split[1]);
@ -78,107 +65,38 @@ export class UserResolver {
console.error('Cannot revoke token'); console.error('Cannot revoke token');
} }
} }
return true;
} }
@get('/users/count') @authorized()
@response(200, { @query(returns => [User])
description: 'User model count', async find(user: Partial<User>): Promise<User[]> {
content: {'application/json': {schema: CountSchema}}, return this.userRepository.find({}); //TODO
})
@authenticate('jwt')
async count(
@param.where(User) where?: Where<User>,
): Promise<Count> {
return this.userRepository.count(where);
} }
@get('/users') @authorized()
@response(200, { @query(returns => User)
description: 'Array of User model instances', async findById(@arg('id', returns => Int) id: number): Promise<User> {
content: { return this.userRepository.findById(id);
'application/json': {
schema: {
type: 'array',
items: getModelSchemaRef(User, {includeRelations: true}),
},
},
},
})
@authenticate('jwt')
async find(
@param.filter(User) filter?: Filter<User>,
): Promise<User[]> {
return this.userRepository.find(filter);
} }
@patch('/users') @authorized()
@response(200, { @mutation(returns => Boolean)
description: 'User PATCH success count', async updateById(@arg('id', returns => Int) id: number, @arg('user') user: UserUpdateInput): Promise<boolean> {
content: {'application/json': {schema: CountSchema}}, if (id === +this.user?.id) { //TODO: this.user
})
@authenticate('jwt')
async updateAll(
@requestBody({
content: {
'application/json': {
schema: getModelSchemaRef(User, {partial: true}),
},
},
})
user: User,
@param.where(User) where?: Where<User>,
): Promise<Count> {
return this.userRepository.updateAll(user, where);
}
@get('/users/{id}')
@response(200, {
description: 'User model instance',
content: {
'application/json': {
schema: getModelSchemaRef(User, {includeRelations: true}),
},
},
})
@authenticate('jwt')
async findById(
@param.path.number('id') id: number,
@param.filter(User, {exclude: 'where'}) filter?: FilterExcludingWhere<User>
): Promise<User> {
return this.userRepository.findById(id, filter);
}
@patch('/users/{id}')
@response(204, {
description: 'User PATCH success',
})
@authenticate('jwt')
async updateById(
@param.path.number('id') id: number,
@requestBody({
content: {
'application/json': {
schema: getModelSchemaRef(User, {partial: true}),
},
},
})
user: User,
): Promise<void> {
if (id === +this.user.id) {
const loggedInUser = await this.userService.findUserById(this.user.id); const loggedInUser = await this.userService.findUserById(this.user.id);
if (user.isAdmin !== undefined && loggedInUser.isAdmin !== user.isAdmin) { if (user.isAdmin !== undefined && loggedInUser.isAdmin !== user.isAdmin) {
throw new HttpErrors.BadRequest('Cannot change admin status of self'); throw new Error('Cannot change admin status of self');
} }
} }
await this.userRepository.updateById(id, user); await this.userRepository.updateById(id, user);
return true;
} }
@del('/users/{id}') @authorized()
@response(204, { @mutation(returns => Boolean)
description: 'User DELETE success', async deleteById(id: number): Promise<Boolean> {
})
@authenticate('jwt')
async deleteById(@param.path.number('id') id: number): Promise<void> {
await this.userRepository.deleteById(id); await this.userRepository.deleteById(id);
}*/ return true;
}
} }

View file

@ -1,37 +1,18 @@
import { field, inputType } from '@loopback/graphql'; import { field, inputType } from '@loopback/graphql';
import { User } from '../../models'; import { User } from '../../models';
import { model, property } from '@loopback/repository'; import { model, property } from '@loopback/repository';
import { UserProperties } from '../user';
@model() @model()
@inputType() @inputType()
export class UserRegisterInput implements Pick<User, 'email' | 'name' | 'password'> { export class UserRegisterInput implements Pick<User, 'email' | 'name' | 'password'> {
@property({ @property(UserProperties.email)
type: 'string',
required: true,
jsonSchema: {
pattern: /[A-Za-z0-9.+_-]+@[A-Za-z.-_]*(u-szeged.hu)|(szte.hu)/.source
}
})
@field() @field()
email: string; email: string;
@property({ @property(UserProperties.name)
type: 'string',
required: true,
jsonSchema: {
pattern: /([A-Za-z-.]+ )+[A-Za-z-.]+/.source
}
})
@field() @field()
name: string; name: string;
@property({ @property(UserProperties.password)
type: 'string',
required: true,
jsonSchema: {
minLength: 8,
maxLength: 255
},
hidden: true
})
@field() @field()
password: string; password: string;
} }

View file

@ -0,0 +1,19 @@
import { User } from '../../models';
import { field, inputType } from '@loopback/graphql';
import { UserProperties } from '../user';
import { property } from '@loopback/repository';
@inputType()
export class UserUpdateInput implements Partial<Pick<User, 'name' | 'email' | 'password' | 'isAdmin'>> {
@field({nullable: true})
@property(UserProperties.email)
email?: string;
@field({nullable: true})
@property(UserProperties.name)
name?: string;
@field({nullable: true})
@property(UserProperties.password)
password?: string;
@field({nullable: true})
isAdmin?: boolean;
}

View file

@ -0,0 +1,17 @@
import { field, inputType, objectType } from '@loopback/graphql';
@inputType()
export class ListInput {
@field()
offset: number;
@field()
limit: number;
}
@objectType()
export class ListResponse<T> {
@field()
count: number;
@field()
list: T[];
}

View file

@ -1,6 +1,53 @@
import { User } from '../models'; import { User } from '../models';
import { field, objectType } from '@loopback/graphql';
export class LoginResult { @objectType()
token: string; export class UserResult implements Pick<User, 'id' | 'email' | 'name' | 'isAdmin'> {
user: Omit<User, 'id' | 'password'>; @field()
email: string;
@field()
id?: number;
@field()
isAdmin: boolean;
@field()
name: string;
} }
@objectType()
export class LoginResult {
@field()
token: string;
@field()
user: UserResult;
}
export const UserProperties = {
id: {
type: 'number',
id: true,
generated: true,
},
email: {
type: 'string',
required: true,
jsonSchema: {
pattern: /[A-Za-z\d.+_-]+@[A-Za-z.-_]*(u-szeged.hu)|(szte.hu)/.source
}
},
name: {
type: 'string',
required: true,
jsonSchema: {
pattern: /([A-Za-z-.]+ )+[A-Za-z-.]+/.source
}
},
password: {
type: 'string',
required: true,
jsonSchema: {
minLength: 8,
maxLength: 255
},
hidden: true
}
};

View file

@ -2,47 +2,24 @@ import { Entity, hasMany, model, property } from '@loopback/repository';
import { Course } from './course.model'; import { Course } from './course.model';
import { CourseUser } from './course-user.model'; import { CourseUser } from './course-user.model';
import { field, objectType } from '@loopback/graphql'; import { field, objectType } from '@loopback/graphql';
import { UserProperties } from '../graphql-types/user';
@model() @model()
@objectType() @objectType()
export class User extends Entity { export class User extends Entity {
@property({ @property(UserProperties.id)
type: 'number',
id: true,
generated: true,
})
@field() @field()
id?: number; id?: number;
@property({ @property(UserProperties.email)
type: 'string',
required: true,
jsonSchema: {
pattern: /[A-Za-z0-9.+_-]+@[A-Za-z.-_]*(u-szeged.hu)|(szte.hu)/.source
}
})
@field() @field()
email: string; email: string;
@property({ @property(UserProperties.name)
type: 'string',
required: true,
jsonSchema: {
pattern: /([A-Za-z-.]+ )+[A-Za-z-.]+/.source
}
})
@field() @field()
name: string; name: string;
@property({ @property(UserProperties.password)
type: 'string',
required: true,
jsonSchema: {
minLength: 8,
maxLength: 255
},
hidden: true
})
@field() @field()
password: string; password: string;