Fix test for User firstName & lastName
Also fix CSRF token. This was probably broken since csurf middleware was modified to use cookie instead of session storage to provide support for single page app (SPA).
This commit is contained in:
parent
6fb69e40df
commit
30a8c56119
@ -11,6 +11,7 @@ export interface IInputProps {
|
||||
readOnly?: boolean
|
||||
label: string
|
||||
Icon?: IconType
|
||||
required?: boolean
|
||||
}
|
||||
|
||||
export class Input extends React.PureComponent<IInputProps> {
|
||||
@ -33,6 +34,7 @@ export class Input extends React.PureComponent<IInputProps> {
|
||||
onChange={this.handleChange}
|
||||
placeholder={this.props.placeholder}
|
||||
readOnly={!!this.props.readOnly}
|
||||
required={this.props.required}
|
||||
/>
|
||||
{Icon && <span className='icon is-left is-small'>
|
||||
<Icon />
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import {IAction, IErrorAction, ActionTypes} from '../actions'
|
||||
import {IAPIDef, ICredentials, IUser} from '@rondo/common'
|
||||
import {IAPIDef, ICredentials, INewUser, IUser} from '@rondo/common'
|
||||
import {IHTTPClient} from '../http/IHTTPClient'
|
||||
|
||||
export enum LoginActionKeys {
|
||||
@ -44,7 +44,7 @@ export class LoginActions {
|
||||
}
|
||||
}
|
||||
|
||||
register = (profile: ICredentials):
|
||||
register = (profile: INewUser):
|
||||
IAction<IUser, LoginActionKeys.LOGIN_REGISTER> => {
|
||||
return {
|
||||
payload: this.http.post('/auth/register', profile),
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import {ICredentials, IUser} from '@rondo/common'
|
||||
import {INewUser, IUser} from '@rondo/common'
|
||||
import {Input} from '../components/Input'
|
||||
import {Redirect} from '../components/Redirect'
|
||||
|
||||
@ -7,7 +7,7 @@ export interface IRegisterFormProps {
|
||||
error?: string
|
||||
onSubmit: () => void
|
||||
onChange: (name: string, value: string) => void
|
||||
data: ICredentials
|
||||
data: INewUser
|
||||
user?: IUser
|
||||
redirectTo: string
|
||||
}
|
||||
@ -21,12 +21,13 @@ export class RegisterForm extends React.PureComponent<IRegisterFormProps> {
|
||||
<form onSubmit={this.props.onSubmit}>
|
||||
<p className='error'>{this.props.error}</p>
|
||||
<Input
|
||||
label='Username'
|
||||
label='Email'
|
||||
name='username'
|
||||
type='email'
|
||||
onChange={this.props.onChange}
|
||||
value={this.props.data.username}
|
||||
placeholder='Email'
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
label='Password'
|
||||
@ -35,6 +36,25 @@ export class RegisterForm extends React.PureComponent<IRegisterFormProps> {
|
||||
onChange={this.props.onChange}
|
||||
value={this.props.data.password}
|
||||
placeholder='Password'
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
label='First Name'
|
||||
name='firstName'
|
||||
type='text'
|
||||
onChange={this.props.onChange}
|
||||
value={this.props.data.firstName}
|
||||
placeholder='First name'
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
label='Last Name'
|
||||
name='lastName'
|
||||
type='text'
|
||||
onChange={this.props.onChange}
|
||||
value={this.props.data.lastName}
|
||||
placeholder='First name'
|
||||
required
|
||||
/>
|
||||
<input
|
||||
className='button is-primary'
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import {ICredentials} from './ICredentials'
|
||||
import {INewUser} from './INewUser'
|
||||
import {ITeam} from './ITeam'
|
||||
import {IUserTeam} from './IUserTeam'
|
||||
import {IUser} from './IUser'
|
||||
@ -6,7 +7,7 @@ import {IUser} from './IUser'
|
||||
export interface IAPIDef {
|
||||
'/auth/register': {
|
||||
'post': {
|
||||
body: ICredentials
|
||||
body: INewUser
|
||||
response: IUser
|
||||
}
|
||||
}
|
||||
|
||||
6
packages/common/src/INewUser.ts
Normal file
6
packages/common/src/INewUser.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import {ICredentials} from './ICredentials'
|
||||
|
||||
export interface INewUser extends ICredentials {
|
||||
firstName: string
|
||||
lastName: string
|
||||
}
|
||||
@ -1,3 +1,6 @@
|
||||
export interface IUser {
|
||||
id: number
|
||||
username: string
|
||||
firstName: string
|
||||
lastName: string
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
export * from './IAPIDef'
|
||||
export * from './ICredentials'
|
||||
export * from './INewUser'
|
||||
export * from './IRequestParams'
|
||||
export * from './IRole'
|
||||
export * from './IRoutes'
|
||||
|
||||
@ -13,17 +13,22 @@ describe('passport.promise', () => {
|
||||
beforeEach(() => {
|
||||
app = express()
|
||||
|
||||
const userInfo = {
|
||||
username: 'test@user.com',
|
||||
firstName: 'test',
|
||||
lastName: 'test',
|
||||
}
|
||||
const userService = new (class implements IUserService {
|
||||
async createUser() {
|
||||
return {id: 1}
|
||||
return {id: 1, ...userInfo}
|
||||
}
|
||||
async changePassword() {/* empty */}
|
||||
async findOne(id: number) {
|
||||
return {id}
|
||||
return {id, ...userInfo}
|
||||
}
|
||||
async validateCredentials({username, password}: ICredentials) {
|
||||
if (username === 'test' && password === 'pass') {
|
||||
return {id: 1}
|
||||
return {id: 1, ...userInfo}
|
||||
return
|
||||
}
|
||||
if (username === 'error') {
|
||||
|
||||
@ -18,6 +18,8 @@ export class LoginRoutes extends BaseRoute<IAPIDef> {
|
||||
const user = await this.userService.createUser({
|
||||
username: req.body.username,
|
||||
password: req.body.password,
|
||||
firstName: req.body.firstName,
|
||||
lastName: req.body.lastName,
|
||||
})
|
||||
await req.logInPromise(user)
|
||||
return user
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import {ICredentials} from '@rondo/common'
|
||||
import {IUser} from '@rondo/common'
|
||||
import {ICredentials, INewUser, IUser} from '@rondo/common'
|
||||
|
||||
export interface IUserService {
|
||||
createUser(credentials: ICredentials): Promise<IUser>
|
||||
createUser(credentials: INewUser): Promise<IUser>
|
||||
changePassword(params: {
|
||||
userId: number,
|
||||
oldPassword: string,
|
||||
|
||||
@ -14,6 +14,8 @@ describe('UserService', () => {
|
||||
return userService.createUser({
|
||||
username: u,
|
||||
password: p,
|
||||
firstName: 'test',
|
||||
lastName: 'test',
|
||||
})
|
||||
}
|
||||
|
||||
@ -23,7 +25,7 @@ describe('UserService', () => {
|
||||
expect(result.id).toBeTruthy()
|
||||
const user = await userService.findOne(result.id)
|
||||
expect(user).toBeTruthy()
|
||||
expect(user!.password).toBe(undefined)
|
||||
expect(user).not.toHaveProperty('password')
|
||||
})
|
||||
|
||||
it('throws when username is not an email', async () => {
|
||||
@ -88,7 +90,7 @@ describe('UserService', () => {
|
||||
await createUser()
|
||||
const user = await userService
|
||||
.validateCredentials({ username, password })
|
||||
expect(user!.password).toBe(undefined)
|
||||
expect(user).not.toHaveProperty('password')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -1,38 +1,68 @@
|
||||
import createError from 'http-errors'
|
||||
import {BaseService} from './BaseService'
|
||||
import {ICredentials} from '@rondo/common'
|
||||
import {ICredentials, INewUser, IUser} from '@rondo/common'
|
||||
import {IUserService} from './IUserService'
|
||||
import {UserEmail} from '../entities/UserEmail'
|
||||
import {User} from '../entities/User'
|
||||
import {compare, hash} from 'bcrypt'
|
||||
import {validate as validateEmail} from 'email-validator'
|
||||
import {Validator, trim} from '../validator'
|
||||
|
||||
const SALT_ROUNDS = 10
|
||||
const MIN_PASSWORD_LENGTH = 10
|
||||
|
||||
export class UserService extends BaseService implements IUserService {
|
||||
async createUser(payload: ICredentials): Promise<User> {
|
||||
const username = payload.username
|
||||
if (!validateEmail(username)) {
|
||||
async createUser(payload: INewUser): Promise<IUser> {
|
||||
const newUser = {
|
||||
username: trim(payload.username),
|
||||
firstName: trim(payload.firstName),
|
||||
lastName: trim(payload.lastName),
|
||||
}
|
||||
|
||||
if (!validateEmail(newUser.username)) {
|
||||
throw createError(400, 'Username is not a valid e-mail')
|
||||
}
|
||||
if (payload.password.length < MIN_PASSWORD_LENGTH) {
|
||||
throw createError(400,
|
||||
`Password must be at least ${MIN_PASSWORD_LENGTH} characters long`)
|
||||
}
|
||||
|
||||
new Validator(newUser)
|
||||
.ensure('username')
|
||||
.ensure('firstName')
|
||||
.ensure('lastName')
|
||||
.throw()
|
||||
|
||||
const password = await this.hash(payload.password)
|
||||
const user = await this.getRepository(User).save({
|
||||
...newUser,
|
||||
password,
|
||||
})
|
||||
await this.getRepository(UserEmail).save({
|
||||
email: username,
|
||||
email: newUser.username,
|
||||
userId: user.id,
|
||||
})
|
||||
return user
|
||||
return {
|
||||
id: user.id,
|
||||
...newUser,
|
||||
}
|
||||
}
|
||||
|
||||
async findOne(id: number) {
|
||||
return this.getRepository(User).findOne(id)
|
||||
const user = await this.getRepository(User).findOne(id, {
|
||||
relations: ['emails'],
|
||||
})
|
||||
|
||||
if (!user) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
username: user.emails[0] ? user.emails[0].email : '',
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
}
|
||||
}
|
||||
|
||||
async findUserByEmail(email: string) {
|
||||
@ -71,6 +101,7 @@ export class UserService extends BaseService implements IUserService {
|
||||
.createQueryBuilder('user')
|
||||
.select('user')
|
||||
.addSelect('user.password')
|
||||
.addSelect('emails')
|
||||
.innerJoin('user.emails', 'emails', 'emails.email = :email', {
|
||||
email: username,
|
||||
})
|
||||
@ -78,8 +109,12 @@ export class UserService extends BaseService implements IUserService {
|
||||
|
||||
const isValid = await compare(password, user ? user.password! : '')
|
||||
if (user && isValid) {
|
||||
delete user.password
|
||||
return user
|
||||
return {
|
||||
id: user.id,
|
||||
username: user.emails[0].email,
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -75,7 +75,7 @@ export class TestUtils<T extends IRoutes> {
|
||||
const response = await supertest(this.app)
|
||||
.get(`${context}/app`)
|
||||
.expect(200)
|
||||
const cookie = response.header['set-cookie'] as string
|
||||
const cookie = this.getCookies(response.header['set-cookie'])
|
||||
const token = this.getCsrfToken(response.text)
|
||||
expect(cookie).toBeTruthy()
|
||||
expect(token).toBeTruthy()
|
||||
@ -101,11 +101,17 @@ export class TestUtils<T extends IRoutes> {
|
||||
const response = await supertest(this.app)
|
||||
.post(`${context}/api/auth/register`)
|
||||
.set('cookie', cookie)
|
||||
.send(this.getLoginBody(token))
|
||||
.send({
|
||||
firstName: 'test',
|
||||
lastName: 'test',
|
||||
...this.getLoginBody(token),
|
||||
})
|
||||
.expect(200)
|
||||
|
||||
const cookies = this.getCookies(response.header['set-cookie'])
|
||||
|
||||
return {
|
||||
cookie: response.header['set-cookie'] as string,
|
||||
cookie: [cookies, cookie].join('; '),
|
||||
userId: response.body.id,
|
||||
token,
|
||||
}
|
||||
@ -121,7 +127,12 @@ export class TestUtils<T extends IRoutes> {
|
||||
.send({username, password, _csrf: token})
|
||||
.expect(200)
|
||||
|
||||
return {cookie: response.header['set-cookie'] as string, token}
|
||||
const cookies = this.getCookies(response.header['set-cookie'])
|
||||
|
||||
return {
|
||||
cookie: [cookies, cookie].join('; '),
|
||||
token,
|
||||
}
|
||||
}
|
||||
|
||||
request = (baseUrl = '') => {
|
||||
@ -130,4 +141,8 @@ export class TestUtils<T extends IRoutes> {
|
||||
`${this.bootstrap.config.app.baseUrl.path!}${baseUrl}`)
|
||||
}
|
||||
|
||||
private getCookies(setCookiesString: string[]): string {
|
||||
return setCookiesString.map(c => c.split('; ')[0]).join('; ')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export * from './IValidationMessage'
|
||||
export * from './ValidationError'
|
||||
export * from './Validator'
|
||||
export * from './trim'
|
||||
|
||||
9
packages/server/src/validator/trim.test.ts
Normal file
9
packages/server/src/validator/trim.test.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import {trim} from './trim'
|
||||
|
||||
describe('trim', () => {
|
||||
it('trims string', () => {
|
||||
expect(trim('test')).toEqual('test')
|
||||
expect(trim(' test ')).toEqual('test')
|
||||
expect(trim(undefined)).toEqual('')
|
||||
})
|
||||
})
|
||||
6
packages/server/src/validator/trim.ts
Normal file
6
packages/server/src/validator/trim.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export function trim(str?: string) {
|
||||
if (!str) {
|
||||
return ''
|
||||
}
|
||||
return str.trim()
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user