-
Notifications
You must be signed in to change notification settings - Fork 208
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: extract local credentials into a new model
Introduce `UserCredentials` models to hold hashed passwords, add has-one relation from `User` to `UserCredentials`. Rework authentication-related code to work with the new domain model. Signed-off-by: Miroslav Bajtoš <[email protected]>
- Loading branch information
Showing
14 changed files
with
229 additions
and
78 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,17 +18,19 @@ import { | |
import {setupApplication} from './helper'; | ||
import {TokenService, UserService} from '@loopback/authentication'; | ||
import {securityId} from '@loopback/security'; | ||
import * as _ from 'lodash'; | ||
|
||
describe('authentication services', () => { | ||
let app: ShoppingApplication; | ||
|
||
const user = { | ||
const userData = { | ||
email: '[email protected]', | ||
password: 'p4ssw0rd', | ||
firstName: 'unit', | ||
lastName: 'test', | ||
}; | ||
|
||
const userPassword = 'p4ssw0rd'; | ||
|
||
let newUser: User; | ||
let jwtService: TokenService; | ||
let userService: UserService<User, Credentials>; | ||
|
@@ -70,19 +72,15 @@ describe('authentication services', () => { | |
|
||
it('user service verifyCredentials() succeeds', async () => { | ||
const {email} = newUser; | ||
const credentials = {email, password: user.password}; | ||
const credentials = {email, password: userPassword}; | ||
|
||
const returnedUser = await userService.verifyCredentials(credentials); | ||
|
||
// create a copy of returned user without password field | ||
const returnedUserWithOutPassword = Object.assign({}, returnedUser, { | ||
password: user.password, | ||
}); | ||
delete returnedUserWithOutPassword.password; | ||
const returnedUserWithOutPassword = _.omit(returnedUser, 'password'); | ||
|
||
// create a copy of expected user without password field | ||
const expectedUserWithoutPassword = Object.assign({}, newUser); | ||
delete expectedUserWithoutPassword.password; | ||
const expectedUserWithoutPassword = _.omit(newUser, 'password'); | ||
|
||
expect(returnedUserWithOutPassword).to.deepEqual( | ||
expectedUserWithoutPassword, | ||
|
@@ -178,21 +176,21 @@ describe('authentication services', () => { | |
}); | ||
|
||
it('password encrypter hashPassword() succeeds', async () => { | ||
const encrypedPassword = await bcryptHasher.hashPassword(user.password); | ||
expect(encrypedPassword).to.not.equal(user.password); | ||
const encrypedPassword = await bcryptHasher.hashPassword(userPassword); | ||
expect(encrypedPassword).to.not.equal(userPassword); | ||
}); | ||
|
||
it('password encrypter compare() succeeds', async () => { | ||
const encrypedPassword = await bcryptHasher.hashPassword(user.password); | ||
const encrypedPassword = await bcryptHasher.hashPassword(userPassword); | ||
const passwordsAreTheSame = await bcryptHasher.comparePassword( | ||
user.password, | ||
userPassword, | ||
encrypedPassword, | ||
); | ||
expect(passwordsAreTheSame).to.be.True(); | ||
}); | ||
|
||
it('password encrypter compare() fails', async () => { | ||
const encrypedPassword = await bcryptHasher.hashPassword(user.password); | ||
const encrypedPassword = await bcryptHasher.hashPassword(userPassword); | ||
const passwordsAreTheSame = await bcryptHasher.comparePassword( | ||
'someotherpassword', | ||
encrypedPassword, | ||
|
@@ -208,12 +206,14 @@ describe('authentication services', () => { | |
|
||
async function createUser() { | ||
bcryptHasher = await app.get(PasswordHasherBindings.PASSWORD_HASHER); | ||
const encryptedPassword = await bcryptHasher.hashPassword(user.password); | ||
newUser = await userRepo.create( | ||
Object.assign({}, user, {password: encryptedPassword}), | ||
); | ||
const encryptedPassword = await bcryptHasher.hashPassword(userPassword); | ||
newUser = await userRepo.create(userData); | ||
// MongoDB returns an id object we need to convert to string | ||
newUser.id = newUser.id.toString(); | ||
|
||
await userRepo.userCredentials(newUser.id).create({ | ||
password: encryptedPassword, | ||
}); | ||
} | ||
|
||
async function clearDatabase() { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,12 +16,13 @@ describe('authorization', () => { | |
let client: Client; | ||
let userRepo: UserRepository; | ||
|
||
let user = { | ||
let userData = { | ||
email: '[email protected]', | ||
password: 'p4ssw0rd', | ||
firstName: 'customer_service', | ||
}; | ||
|
||
const userPassword = 'p4ssw0rd'; | ||
|
||
let passwordHasher: PasswordHasher; | ||
let newUser: User; | ||
let token: string; | ||
|
@@ -54,7 +55,7 @@ describe('authorization', () => { | |
|
||
let res = await client | ||
.post('/users/login') | ||
.send({email: newUser.email, password: user.password}) | ||
.send({email: newUser.email, password: userPassword}) | ||
.expect(200); | ||
|
||
token = res.body.token; | ||
|
@@ -82,9 +83,8 @@ describe('authorization', () => { | |
|
||
describe('bob', () => { | ||
it('allows bob create orders', async () => { | ||
user = { | ||
userData = { | ||
email: '[email protected]', | ||
password: 'p4ssw0rd', | ||
firstName: 'bob', | ||
}; | ||
newUser = await createAUser(); | ||
|
@@ -102,7 +102,7 @@ describe('authorization', () => { | |
|
||
let res = await client | ||
.post('/users/login') | ||
.send({email: newUser.email, password: user.password}) | ||
.send({email: newUser.email, password: userPassword}) | ||
.expect(200); | ||
|
||
token = res.body.token; | ||
|
@@ -145,13 +145,15 @@ describe('authorization', () => { | |
} | ||
|
||
async function createAUser() { | ||
const encryptedPassword = await passwordHasher.hashPassword(user.password); | ||
const aUser = await userRepo.create( | ||
Object.assign({}, user, {password: encryptedPassword}), | ||
); | ||
const encryptedPassword = await passwordHasher.hashPassword(userPassword); | ||
const aUser = await userRepo.create(userData); | ||
|
||
// MongoDB returns an id object we need to convert to string | ||
aUser.id = aUser.id.toString(); | ||
|
||
await userRepo.userCredentials(aUser.id).create({ | ||
password: encryptedPassword, | ||
}); | ||
return aUser; | ||
} | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,10 +16,11 @@ describe('UserOrderController acceptance tests', () => { | |
|
||
const userData = { | ||
email: '[email protected]', | ||
password: 'p4ssw0rd', | ||
firstName: 'customer_service', | ||
}; | ||
|
||
const userPassword = 'p4ssw0rd'; | ||
|
||
before('setupApplication', async () => { | ||
({app, client} = await setupApplication()); | ||
}); | ||
|
@@ -158,22 +159,24 @@ describe('UserOrderController acceptance tests', () => { | |
const passwordHasher = await app.get( | ||
PasswordHasherBindings.PASSWORD_HASHER, | ||
); | ||
const encryptedPassword = await passwordHasher.hashPassword( | ||
userData.password, | ||
); | ||
const encryptedPassword = await passwordHasher.hashPassword(userPassword); | ||
|
||
const newUser = await userRepo.create(userData); | ||
|
||
const newUser = await userRepo.create( | ||
Object.assign({}, userData, {password: encryptedPassword}), | ||
); | ||
// MongoDB returns an id object we need to convert to string | ||
newUser.id = newUser.id.toString(); | ||
|
||
await userRepo.userCredentials(newUser.id).create({ | ||
password: encryptedPassword, | ||
}); | ||
|
||
return newUser; | ||
} | ||
|
||
async function authenticateUser(user: User) { | ||
const res = await client | ||
.post('/users/login') | ||
.send({email: user.email, password: userData.password}); | ||
.send({email: user.email, password: userPassword}); | ||
|
||
const token = res.body.token; | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,13 +27,14 @@ describe('UserController', () => { | |
|
||
let userRepo: UserRepository; | ||
|
||
const user = { | ||
const userData = { | ||
email: '[email protected]', | ||
password: 'p4ssw0rd', | ||
firstName: 'Example', | ||
lastName: 'User', | ||
}; | ||
|
||
const userPassword = 'p4ssw0rd'; | ||
|
||
let passwordHasher: PasswordHasher; | ||
let expiredToken: string; | ||
|
||
|
@@ -54,7 +55,7 @@ describe('UserController', () => { | |
it('creates new user when POST /users is invoked', async () => { | ||
const res = await client | ||
.post('/users') | ||
.send(user) | ||
.send({...userData, password: userPassword}) | ||
.expect(200); | ||
|
||
// Assertions | ||
|
@@ -65,6 +66,20 @@ describe('UserController', () => { | |
expect(res.body).to.not.have.property('password'); | ||
}); | ||
|
||
it('creates a new user with the given id', async () => { | ||
// This test verifies the scenario described in our docs, see | ||
// https://loopback.io/doc/en/lb4/Authentication-Tutorial.html | ||
const res = await client.post('/users').send({ | ||
id: '5dd6acee242760334f6aef65', | ||
...userData, | ||
password: userPassword, | ||
}); | ||
expect(res.body).to.deepEqual({ | ||
id: '5dd6acee242760334f6aef65', | ||
...userData, | ||
}); | ||
}); | ||
|
||
it('throws error for POST /users with a missing email', async () => { | ||
const res = await client | ||
.post('/users') | ||
|
@@ -122,19 +137,18 @@ describe('UserController', () => { | |
it('throws error for POST /users with an existing email', async () => { | ||
await client | ||
.post('/users') | ||
.send(user) | ||
.send({...userData, password: userPassword}) | ||
.expect(200); | ||
const res = await client | ||
.post('/users') | ||
.send(user) | ||
.send({...userData, password: userPassword}) | ||
.expect(409); | ||
|
||
expect(res.body.error.message).to.equal('Email value is already taken'); | ||
}); | ||
|
||
it('returns a user with given id when GET /users/{id} is invoked', async () => { | ||
const newUser = await createAUser(); | ||
delete newUser.password; | ||
delete newUser.orders; | ||
|
||
await client.get(`/users/${newUser.id}`).expect(200, newUser.toJSON()); | ||
|
@@ -146,7 +160,7 @@ describe('UserController', () => { | |
|
||
const res = await client | ||
.post('/users/login') | ||
.send({email: newUser.email, password: user.password}) | ||
.send({email: newUser.email, password: userPassword}) | ||
.expect(200); | ||
|
||
const token = res.body.token; | ||
|
@@ -158,7 +172,7 @@ describe('UserController', () => { | |
|
||
const res = await client | ||
.post('/users/login') | ||
.send({email: '[email protected]', password: user.password}) | ||
.send({email: '[email protected]', password: userPassword}) | ||
.expect(401); | ||
|
||
expect(res.body.error.message).to.equal('Invalid email or password.'); | ||
|
@@ -180,7 +194,7 @@ describe('UserController', () => { | |
|
||
let res = await client | ||
.post('/users/login') | ||
.send({email: newUser.email, password: user.password}) | ||
.send({email: newUser.email, password: userPassword}) | ||
.expect(200); | ||
|
||
const token = res.body.token; | ||
|
@@ -280,13 +294,15 @@ describe('UserController', () => { | |
} | ||
|
||
async function createAUser() { | ||
const encryptedPassword = await passwordHasher.hashPassword(user.password); | ||
const newUser = await userRepo.create( | ||
Object.assign({}, user, {password: encryptedPassword}), | ||
); | ||
const encryptedPassword = await passwordHasher.hashPassword(userPassword); | ||
const newUser = await userRepo.create(userData); | ||
// MongoDB returns an id object we need to convert to string | ||
newUser.id = newUser.id.toString(); | ||
|
||
await userRepo.userCredentials(newUser.id).create({ | ||
password: encryptedPassword, | ||
}); | ||
|
||
return newUser; | ||
} | ||
|
||
|
Oops, something went wrong.