Rename old UserService to AuthService, LoginRoutes to AuthRoutes

This commit is contained in:
Jerko Steiner 2019-09-05 10:19:24 +07:00
parent 0a1eabadd9
commit 5cf54d0be9
15 changed files with 128 additions and 114 deletions

View File

@ -20,7 +20,7 @@ export interface IAPIDef {
'/auth/logout': {
'get': {}
}
'/users/password': {
'/auth/password': {
'post': {
body: {
oldPassword: string

View File

@ -1,9 +1,9 @@
import {IUserService} from '../services'
import {IAuthService} from '../services'
import {ITeamService} from '../team'
import {IUserPermissions} from '../user'
export interface IServices {
userService: IUserService
authService: IAuthService
teamService: ITeamService
userPermissions: IUserPermissions
}

View File

@ -26,12 +26,12 @@ export const configureServer: ServerConfigurator = (config, database) => {
const logger = loggerFactory.getLogger('api')
const services: IServices = {
userService: new Services.UserService(database),
authService: new Services.AuthService(database),
teamService: new Team.TeamService(database),
userPermissions: new User.UserPermissions(database),
}
const authenticator = new Middleware.Authenticator(services.userService)
const authenticator = new Middleware.Authenticator(services.authService)
const transactionManager = database.transactionManager
const createTransactionalRouter = <T extends IRoutes>() =>
@ -73,13 +73,13 @@ export const configureServer: ServerConfigurator = (config, database) => {
api: {
path: '/api',
handle: [
new routes.LoginRoutes(
services.userService,
new routes.AuthRoutes(
services.authService,
authenticator,
createTransactionalRouter(),
).handle,
new routes.UserRoutes(
services.userService,
services.authService,
createTransactionalRouter(),
).handle,
new Team.TeamRoutes(

View File

@ -2,11 +2,11 @@ import express, {Application} from 'express'
import request from 'supertest'
import {Authenticator} from './Authenticator'
import {ICredentials} from '@rondo.dev/common'
import {IUserService} from '../services'
import {IAuthService} from '../services'
import {handlePromise} from './handlePromise'
import {urlencoded} from 'body-parser'
describe('passport.promise', () => {
describe('Authenticator', () => {
let app: Application
let loginMiddleware: any
@ -18,7 +18,7 @@ describe('passport.promise', () => {
firstName: 'test',
lastName: 'test',
}
const userService = new (class implements IUserService {
const authService = new (class implements IAuthService {
async createUser() {
return {id: 1, ...userInfo}
}
@ -40,7 +40,7 @@ describe('passport.promise', () => {
return undefined
}
})()
const authenticator = new Authenticator(userService)
const authenticator = new Authenticator(authService)
app.use(urlencoded({ extended: false }))
app.use(authenticator.handle)

View File

@ -1,5 +1,5 @@
import {Authenticator as A, Passport} from 'passport'
import {IUserService} from '../services'
import {IAuthService} from '../services'
import {Strategy as LocalStrategy} from 'passport-local'
import {THandler} from './THandler'
import {IMiddleware} from './IMiddleware'
@ -9,7 +9,7 @@ export class Authenticator implements IMiddleware {
protected readonly passport: A
readonly handle: THandler[]
constructor(protected readonly userService: IUserService) {
constructor(protected readonly authService: IAuthService) {
this.passport = new Passport() as any
this.configurePassport()
@ -45,7 +45,7 @@ export class Authenticator implements IMiddleware {
protected deserializeUser =
// TODO parametrize user type
(userId: number, done: (err?: Error, user?: any) => void) => {
this.userService.findOne(userId)
this.authService.findOne(userId)
.then(user => done(undefined, user))
.catch(done)
}
@ -67,7 +67,7 @@ export class Authenticator implements IMiddleware {
password: string,
done: (err?: Error, user?: any) => void,
) => {
this.userService.validateCredentials({ username, password })
this.authService.validateCredentials({ username, password })
.then(user => done(undefined, user))
.catch(done)
}

View File

@ -0,0 +1,78 @@
import {test} from '../test'
describe('/auth', () => {
test.withDatabase()
describe('/register', () => {
it('should create a new user account', async () => {
await test.registerAccount()
})
})
describe('/login', () => {
beforeEach(async () => {
await test.registerAccount()
})
it('should log in the newly created user', async () => {
await test.login()
})
})
describe('/auth/password', () => {
const t = test.request('/api')
beforeEach(async () => {
const session = await test.registerAccount()
const token = session.token
const cookie = session.cookie
t.setHeaders({cookie, 'x-csrf-token': token})
})
it('should prevent access when user not logged in', async () => {
const {cookie, token} = await test.getCsrf()
await t
.setHeaders({'cookie': cookie, 'x-csrf-token': token})
.post('/auth/password')
.expect(401)
})
describe('POST /users/password', () => {
it('changes user password when passwords match', async () => {
await t
.post('/auth/password')
.send({ oldPassword: test.password, newPassword: 'newPass' })
.expect(200)
await test.login(test.username, 'newPass')
})
it('returns 400 when passwords do not match', async () => {
await t
.post('/auth/password')
.send({ oldPassword: 'invalid-password', newPassword: 'newPass' })
.expect(400)
})
})
})
describe('/logout', () => {
let cookie!: string
beforeEach(async () => {
await test.registerAccount()
cookie = (await test.login()).cookie
})
it('should log out the user', async () => {
await test.request('/api')
.get('/auth/logout')
.set('cookie', cookie)
.expect(200)
})
})
})

View File

@ -1,12 +1,13 @@
import {AsyncRouter} from '../router'
import {BaseRoute} from './BaseRoute'
import {IAPIDef} from '@rondo.dev/common'
import {IUserService} from '../services'
import {IAuthService} from '../services'
import {Authenticator} from '../middleware'
import {ensureLoggedInApi} from '../middleware'
export class LoginRoutes extends BaseRoute<IAPIDef> {
export class AuthRoutes extends BaseRoute<IAPIDef> {
constructor(
protected readonly userService: IUserService,
protected readonly authService: IAuthService,
protected readonly authenticator: Authenticator,
protected readonly t: AsyncRouter<IAPIDef>,
) {
@ -15,7 +16,7 @@ export class LoginRoutes extends BaseRoute<IAPIDef> {
setup(t: AsyncRouter<IAPIDef>) {
t.post('/auth/register', async (req, res) => {
const user = await this.userService.createUser({
const user = await this.authService.createUser({
username: req.body.username,
password: req.body.password,
firstName: req.body.firstName,
@ -37,6 +38,14 @@ export class LoginRoutes extends BaseRoute<IAPIDef> {
return user
})
t.post('/auth/password', [ensureLoggedInApi], async req => {
await this.authService.changePassword({
userId: req.user!.id,
oldPassword: req.body.oldPassword,
newPassword: req.body.newPassword,
})
})
t.get('/auth/logout', async (req, res) => {
req.logout()
})

View File

@ -1,40 +0,0 @@
import {test} from '../test'
describe('login', () => {
test.withDatabase()
describe('/register', () => {
it('should create a new user account', async () => {
await test.registerAccount()
})
})
describe('/login', () => {
beforeEach(async () => {
await test.registerAccount()
})
it('should log in the newly created user', async () => {
await test.login()
})
})
describe('/logout', () => {
let cookie!: string
beforeEach(async () => {
await test.registerAccount()
cookie = (await test.login()).cookie
})
it('should log out the user', async () => {
await test.request('/api')
.get('/auth/logout')
.set('cookie', cookie)
.expect(200)
})
})
})

View File

@ -15,31 +15,6 @@ describe('user', () => {
t.setHeaders({ cookie, 'x-csrf-token': token })
})
it('should prevent access when user not logged in', async () => {
await t
.setHeaders({ token })
.get(`/users/password`)
.expect(401)
})
describe('POST /users/password', () => {
it('changes user password when passwords match', async () => {
await t
.post('/users/password')
.send({ oldPassword: test.password, newPassword: 'newPass' })
.expect(200)
await test.login(test.username, 'newPass')
})
it('returns 400 when passwords do not match', async () => {
await t
.post('/users/password')
.send({ oldPassword: 'invalid-password', newPassword: 'newPass' })
.expect(400)
})
})
describe('GET /users/profile', () => {
it('fetches user profile', async () => {
t.setHeaders({ cookie })

View File

@ -1,12 +1,12 @@
import {AsyncRouter} from '../router'
import {BaseRoute} from './BaseRoute'
import {IAPIDef} from '@rondo.dev/common'
import {IUserService} from '../services'
import {IAuthService} from '../services'
import {ensureLoggedInApi} from '../middleware'
export class UserRoutes extends BaseRoute<IAPIDef> {
constructor(
protected readonly userService: IUserService,
protected readonly userService: IAuthService,
protected readonly t: AsyncRouter<IAPIDef>,
) {
super(t)
@ -15,14 +15,6 @@ export class UserRoutes extends BaseRoute<IAPIDef> {
setup(t: AsyncRouter<IAPIDef>) {
t.use('/users', ensureLoggedInApi)
t.post('/users/password', async req => {
await this.userService.changePassword({
userId: req.user!.id,
oldPassword: req.body.oldPassword,
newPassword: req.body.newPassword,
})
})
t.get('/users/emails/:email', async req => {
return this.userService.findUserByEmail(req.params.email)
})

View File

@ -1,4 +1,4 @@
export * from './BaseRoute'
export * from './LoginRoutes'
export * from './UserRoutes'
export * from './application'
export * from './AuthRoutes'
export * from './BaseRoute'
export * from './UserRoutes'

View File

@ -1,17 +1,17 @@
import {test} from '../test'
import {UserService} from './UserService'
import {AuthService} from './AuthService'
describe('UserService', () => {
describe('AuthService', () => {
test.withDatabase()
const username = test.username
const password = '1234567890'
const userService = new UserService(test.bootstrap.database)
const authService = new AuthService(test.bootstrap.database)
async function createUser(u = username, p = password) {
return userService.createUser({
return authService.createUser({
username: u,
password: p,
firstName: 'test',
@ -23,7 +23,7 @@ describe('UserService', () => {
it('creates a new user with bcrypted password', async () => {
const result = await createUser()
expect(result.id).toBeTruthy()
const user = await userService.findOne(result.id)
const user = await authService.findOne(result.id)
expect(user).toBeTruthy()
expect(user).not.toHaveProperty('password')
})
@ -48,7 +48,7 @@ describe('UserService', () => {
describe('findUserByMail', () => {
it('returns user without password', async () => {
await createUser()
const user = await userService.findUserByEmail(username)
const user = await authService.findUserByEmail(username)
expect(user).toBeTruthy()
expect(user).not.toHaveProperty('password')
})
@ -57,7 +57,7 @@ describe('UserService', () => {
describe('getUserEmails', () => {
it('returns user emails', async () => {
const {id} = await createUser()
const emails = await userService.findUserEmails(id)
const emails = await authService.findUserEmails(id)
expect(emails).toEqual([{
id: jasmine.any(Number),
userId: id,
@ -71,24 +71,24 @@ describe('UserService', () => {
describe('validateCredentials', () => {
it('returns user when password is valid', async () => {
await createUser()
expect(await userService.validateCredentials({ username, password }))
expect(await authService.validateCredentials({ username, password }))
.toBeTruthy()
})
it('returns undefined when no user', async () => {
expect(await userService.validateCredentials({ username, password }))
expect(await authService.validateCredentials({ username, password }))
.toBe(undefined)
})
it('returns undefined when password is invalid', async () => {
await createUser()
expect(await userService.validateCredentials({ username, password: 't' }))
expect(await authService.validateCredentials({ username, password: 't' }))
.toBe(undefined)
})
it('does not return a password', async () => {
await createUser()
const user = await userService
const user = await authService
.validateCredentials({ username, password })
expect(user).not.toHaveProperty('password')
})

View File

@ -2,7 +2,7 @@ import createError from 'http-errors'
import {BaseService} from './BaseService'
import {IDatabase} from '../database/IDatabase'
import {ICredentials, INewUser, IUser, trim} from '@rondo.dev/common'
import {IUserService} from './IUserService'
import {IAuthService} from './IAuthService'
import {UserEmail} from '../entities/UserEmail'
import {User} from '../entities/User'
import {compare, hash} from 'bcrypt'
@ -12,7 +12,7 @@ import {Validator} from '../validator'
const SALT_ROUNDS = 10
const MIN_PASSWORD_LENGTH = 10
export class UserService implements IUserService {
export class AuthService implements IAuthService {
constructor(protected readonly db: IDatabase) {}
async createUser(payload: INewUser): Promise<IUser> {

View File

@ -1,6 +1,6 @@
import {ICredentials, INewUser, IUser} from '@rondo.dev/common'
export interface IUserService {
export interface IAuthService {
createUser(credentials: INewUser): Promise<IUser>
changePassword(params: {
userId: number,

View File

@ -1,3 +1,3 @@
export * from './BaseService'
export * from './IUserService'
export * from './UserService'
export * from './IAuthService'
export * from './AuthService'