-
Notifications
You must be signed in to change notification settings - Fork 208
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: hash the password #37
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,9 +12,14 @@ import { | |
import {UserRepository, OrderRepository} from '../../src/repositories'; | ||
import {User} from '../../src/models'; | ||
import * as _ from 'lodash'; | ||
import {promisify} from 'util'; | ||
import {hash} from 'bcryptjs'; | ||
import {JsonWebTokenError} from 'jsonwebtoken'; | ||
import {HttpErrors} from '@loopback/rest'; | ||
const hashAsync = promisify(hash); | ||
const SECRET = 'secretforjwt'; | ||
|
||
describe('authentication', () => { | ||
describe('authentication utilities', () => { | ||
const mongodbDS = new MongoDataSource(); | ||
const orderRepo = new OrderRepository(mongodbDS); | ||
const userRepo = new UserRepository(mongodbDS, orderRepo); | ||
|
@@ -26,11 +31,42 @@ describe('authentication', () => { | |
}; | ||
let newUser: User; | ||
|
||
before('create user', async () => { | ||
newUser = await userRepo.create(user); | ||
before(clearDatabase); | ||
before(createUser); | ||
|
||
it('getAccessTokenForUser creates valid jwt access token', async () => { | ||
const token = await getAccessTokenForUser(userRepo, { | ||
email: '[email protected]', | ||
password: 'p4ssw0rd', | ||
}); | ||
expect(token).to.not.be.empty(); | ||
}); | ||
|
||
it('decodes valid access token', async () => { | ||
it('getAccessTokenForUser rejects non-existing user with error Not Found', async () => { | ||
const expectedError = new HttpErrors['NotFound']( | ||
`User with email [email protected] not found.`, | ||
); | ||
return expect( | ||
getAccessTokenForUser(userRepo, { | ||
email: '[email protected]', | ||
password: 'fake', | ||
}), | ||
).to.be.rejectedWith(expectedError); | ||
}); | ||
|
||
it('getAccessTokenForUser rejects wrong credential with error Unauthorized', async () => { | ||
const expectedError = new HttpErrors.Unauthorized( | ||
'The credentials are not correct.', | ||
); | ||
return expect( | ||
getAccessTokenForUser(userRepo, { | ||
email: '[email protected]', | ||
password: 'fake', | ||
}), | ||
).to.be.rejectedWith(expectedError); | ||
}); | ||
|
||
it('decodeAccessToken decodes valid access token', async () => { | ||
const token = await getAccessTokenForUser(userRepo, { | ||
email: '[email protected]', | ||
password: 'p4ssw0rd', | ||
|
@@ -40,15 +76,19 @@ describe('authentication', () => { | |
expect(currentUser).to.deepEqual(expectedUser); | ||
}); | ||
|
||
it('throws error for invalid accesstoken', async () => { | ||
it('decodeAccessToken throws error for invalid accesstoken', async () => { | ||
const token = 'fake'; | ||
try { | ||
await decodeAccessToken(token, SECRET); | ||
expect('throws error').to.be.true(); | ||
} catch (err) { | ||
expect(err.message).to.equal('jwt malformed'); | ||
} | ||
const error = new JsonWebTokenError('jwt malformed'); | ||
return expect(decodeAccessToken(token, SECRET)).to.be.rejectedWith(error); | ||
}); | ||
|
||
async function createUser() { | ||
user.password = await hashAsync(user.password, 10); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jannyHou The higher the number is, the longer it takes to compute the hash. Slow hash computation is good in production as it makes brute-force attacks difficult, but also impractical in tests because it can significantly slow down the test suite. We have been through this in LoopBack 3.x, let's not repeat that mistake again. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @bajtos Thanks for sharing the experience and explain the reason behind using the smallest number!
Will do it in the next PR that extracts hash functions into utils. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
newUser = await userRepo.create(user); | ||
} | ||
async function clearDatabase() { | ||
await userRepo.deleteAll(); | ||
} | ||
}); | ||
|
||
function getExpectedUser(originalUser: User) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should it be the responsibility of the controller/repository to hash passwords?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jannyHou and I were talking about a hash utility function which can be called from the user repo or controller. I think it makes sense for the repository to call it when storing user passwords in the backend, but I see how that can be delegated to a controller too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@raymondfeng @b-admike My opinion is unless we define a specific repository interface for model User and add a function for the hashing purpose, I tend to put the logic in controller. And as @b-admike said, we can extract it into utility/service that can be called from the controller.
Like what we have in authentication utils.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And I plan to refactor them into the util in the next PR, this one focus on fixing the plain password.
Explained in the PR description :)
A follow up PR for #26
And connect to the first acceptance criteria in #35 (there will be another PR after it to refactor the hash utils into a proper place.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jannyHou Please do extract this code into utility/service that can be called from multiple places. For example tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bajtos See the refactor PR #41