-
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
refactor: bcrypt hash #51
Conversation
71e9754
to
671d8cb
Compare
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.
I am confused about the architecture of the tests and why are they calling the hasher function directly. Feel free to leave this discussion for later, after the PR is landed.
I have few comments about the new bindings, PTAL.
src/keys.ts
Outdated
'services.hash_password', | ||
export namespace BcryptHasherBindings { | ||
export const BCRYPT_HASHER = BindingKey.create<BcryptHasher>( | ||
'services.bcrypt.hasher', |
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.
In dependency inversion, the bindings are usually defined via interfaces, to allow consumers to provide any implementation matching the required contract.
I am proposing to change this binding to something like
export const PASSWORD_HASHER = BindingKey.create<PasswordHasher>(
'services.password-hasher'
);
|
||
export interface PasswordHasher<T = string> { | ||
hashPassword(password: T): Promise<T>; | ||
comparePassword(provided_pass: T, stored_pass: T): Promise<boolean>; |
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.
We use camelCase, not snake_case.
comparePassword(provided_pass: T, stored_pass: T): Promise<boolean>; | |
comparePassword(providedPass: T, storedPass: T): Promise<boolean>; |
|
||
async comparePassword( | ||
provided_pass: string, | ||
stored_pass: string, |
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.
Ditto.
src/keys.ts
Outdated
export const BCRYPT_HASHER = BindingKey.create<BcryptHasher>( | ||
'services.bcrypt.hasher', | ||
); | ||
export const ROUND = BindingKey.create<number>( |
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.
export const ROUND = BindingKey.create<number>( | |
export const ROUNDS = BindingKey.create<number>( |
(plural "rounds", not a singular "round")
|
||
before('create new user', async () => { | ||
bcrypt_hasher = new BcryptHasher(4); |
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.
Since this is acceptance-level test, behavior should be configured mostly at app level via bindings.
I think you should change BcryptHasherBindings.ROUNDS
in one of the early hooks to ensure that all tests are using less rounds to hash password when creating new users.
plainPassword = user.password; | ||
user.password = await hashPassword(user.password, 4); | ||
jwt_auth_service = new JWTAuthenticationService(userRepo, JWT_SECRET); | ||
user.password = await bcrypt_hasher.hashPassword(user.password); |
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.
I am confused about this. Why is it necessary to hash the password here? Shouldn't UserRepository take care of hashing the password? At the moment, it's too easy to forget to hash the password before calling userRepo.create
and end up with an invalid user record.
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 The reason is the "hashing password" logic is handled in the user controller, NOT user repository.
When we create the user by making client request POST /Users
, the controller will execute the hashing logic to make sure the hashed password is stored.
While when we call userRepo.create(user)
directly, as what we did in the acceptance test, we need to hash the plain password first then store it.
@@ -84,14 +85,19 @@ describe('authentication utilities', () => { | |||
}); | |||
|
|||
async function createUser() { | |||
user.password = await hashPassword(user.password, 4); | |||
bcrypt_hasher = new BcryptHasher(4); | |||
user.password = await bcrypt_hasher.hashPassword(user.password); |
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.
Similarly here. I would expect that the logic of password hashing should be implemented in a shared piece of code that's called both by runtime parts (repositories/controllers/services) and by tests.
Maybe I am missing something?
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 #51 (comment) ^
d433eb2
to
9920931
Compare
@bajtos Thank you for the review.
True I realized the implemented class is over used in most places, so I replaced it with interface And regarding your concern about the test case, see my comment in #51 (comment). PTAL thanks. |
storedPass: string, | ||
): Promise<boolean> { | ||
const passwordIsMatched = await compare(providedPass, storedPass); | ||
return passwordIsMatched; |
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.
nitpick: can we do return await compare(providedPass, storedPass)
here?
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.
@b-admike Sorry forgot to apply this change, I will remember to do it when refactor this example to use new authentication module.
) {} | ||
|
||
@post('/users') | ||
async create(@requestBody() user: User): Promise<User> { | ||
validateCredentials(_.pick(user, ['email', 'password'])); | ||
user.password = await this.hashPassword(user.password, 10); |
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.
Do we want to set the rounds binding key to 10
for this change?
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 This rounds
is set at the application level as 10 :) see https://github.com/strongloop/loopback4-example-shopping/pull/51/files#diff-8f42631f8d7d8cbaf47074d3ea5536d1R65
src/keys.ts
Outdated
'services.hash_password', | ||
export namespace PasswordHasherBindings { | ||
export const PASSWORD_HASHER = BindingKey.create<PasswordHasher>( | ||
'services.password.hasher', |
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.
nitpick: Is it possible to rename services.password.hasher
to services.hasher.password
so that it's in the namespace hasher
like services.hasher.round
below?
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.
The difference is services.password.hasher
maps to the hasher class, while services.hasher.rounds
maps to a class member.
Maybe I should rename services.password.hasher
to be services.hasher
, does it make more sense?
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.
Your proposal sounds great, but feel free to ignore.
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.
LGTM; feel free to wait for @bajtos as he might have more comments.
Signed-off-by: jannyHou <[email protected]>
9920931
to
7e9095a
Compare
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.
LGTM 👍
A follow up PR for #33
connect to #40
Description
Details see discussion in #33 (comment)