-
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.
Signed-off-by: jannyHou <[email protected]>
- Loading branch information
Showing
10 changed files
with
218 additions
and
109 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
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 |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import {BindingKey} from '@loopback/context'; | ||
import {JWTAuthenticationService} from './services/JWT.authentication.service'; | ||
import {HashPassword} from './services/hash.password.bcryptjs'; | ||
import {JWTStrategy} from './authentication-strategies/JWT.strategy'; | ||
|
||
// Discussion point for reviewers: | ||
// What would be the good naming conversion for bindings? | ||
export namespace JWTAuthenticationBindings { | ||
export const STRATEGY = BindingKey.create<JWTStrategy>( | ||
'authentication.strategies.jwt.strategy', | ||
); | ||
export const SECRET = BindingKey.create<string>('authentication.jwt.secret'); | ||
export const SERVICE = BindingKey.create<JWTAuthenticationService>( | ||
'services.authentication.jwt.service', | ||
); | ||
} | ||
|
||
export namespace OtherServicesBindings { | ||
export const HASH_PASSWORD = BindingKey.create<HashPassword>( | ||
'services.hash_password', | ||
); | ||
} |
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 |
---|---|---|
@@ -0,0 +1,106 @@ | ||
// Copyright IBM Corp. 2018, 2019. All Rights Reserved. | ||
// Node module: @loopback4-example-shopping | ||
// This file is licensed under the MIT License. | ||
// License text available at https://opensource.org/licenses/MIT | ||
|
||
import * as _ from 'lodash'; | ||
import {Credentials, UserRepository} from '../repositories/user.repository'; | ||
import {toJSON} from '@loopback/testlab'; | ||
import {promisify} from 'util'; | ||
import * as isemail from 'isemail'; | ||
import {HttpErrors} from '@loopback/rest'; | ||
import {UserProfile} from '@loopback/authentication'; | ||
import {compare} from 'bcryptjs'; | ||
import {repository} from '@loopback/repository'; | ||
import {inject} from '@loopback/core'; | ||
import {JWTAuthenticationBindings} from '../keys'; | ||
const jwt = require('jsonwebtoken'); | ||
const signAsync = promisify(jwt.sign); | ||
const verifyAsync = promisify(jwt.verify); | ||
|
||
/** | ||
* Constant for JWT secret string | ||
*/ | ||
export const JWT_SECRET = 'jwtsecret'; | ||
|
||
/** | ||
* A JWT authentication service that could be reused by | ||
* different clients. Usually it can be injected in the | ||
* controller constructor. | ||
* It provides services that handle the logics between the controller layer | ||
* and the repository layer. | ||
*/ | ||
export class JWTAuthenticationService { | ||
constructor( | ||
@repository(UserRepository) public userRepository: UserRepository, | ||
@inject(JWTAuthenticationBindings.SECRET) public jwt_secret: string, | ||
) {} | ||
|
||
/** | ||
* A function that retrieves the user with given credentials. Generates | ||
* JWT access token using user profile as payload if user found. | ||
* | ||
* Usually a request's corresponding controller function filters the credential | ||
* fields and invokes this function. | ||
* | ||
* @param credentials The user credentials including email and password. | ||
*/ | ||
async getAccessTokenForUser(credentials: Credentials): Promise<string> { | ||
const foundUser = await this.userRepository.findOne({ | ||
where: {email: credentials.email}, | ||
}); | ||
if (!foundUser) { | ||
throw new HttpErrors['NotFound']( | ||
`User with email ${credentials.email} not found.`, | ||
); | ||
} | ||
const passwordMatched = await compare( | ||
credentials.password, | ||
foundUser.password, | ||
); | ||
if (!passwordMatched) { | ||
throw new HttpErrors.Unauthorized('The credentials are not correct.'); | ||
} | ||
|
||
const currentUser = _.pick(toJSON(foundUser), ['id', 'email', 'firstName']); | ||
// Generate user token using JWT | ||
const token = await signAsync(currentUser, this.jwt_secret, { | ||
expiresIn: 300, | ||
}); | ||
|
||
return token; | ||
} | ||
|
||
/** | ||
* Decodes the user's information from a valid JWT access token. | ||
* Then generate a `UserProfile` instance as the returned user. | ||
* | ||
* @param token A JWT access token. | ||
*/ | ||
async decodeAccessToken(token: string): Promise<UserProfile> { | ||
const decoded = await verifyAsync(token, this.jwt_secret); | ||
let user = _.pick(decoded, ['id', 'email', 'firstName']); | ||
(user as UserProfile).name = user.firstName; | ||
delete user.firstName; | ||
return user; | ||
} | ||
} | ||
|
||
/** | ||
* To be removed in story | ||
* https://github.com/strongloop/loopback4-example-shopping/issues/39 | ||
* @param credentials | ||
*/ | ||
export function validateCredentials(credentials: Credentials) { | ||
// Validate Email | ||
if (!isemail.validate(credentials.email)) { | ||
throw new HttpErrors.UnprocessableEntity('invalid email'); | ||
} | ||
|
||
// Validate Password Length | ||
if (credentials.password.length < 8) { | ||
throw new HttpErrors.UnprocessableEntity( | ||
'password must be minimum 8 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 |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import {genSalt, hash} from 'bcryptjs'; | ||
|
||
/** | ||
* Service HashPassword using module 'bcryptjs'. | ||
* It takes in a plain password, generates a salt with given | ||
* round and returns the hashed password as a string | ||
*/ | ||
export type HashPassword = ( | ||
password: string, | ||
rounds: number, | ||
) => Promise<string>; | ||
// bind function to `services.bcryptjs.HashPassword` | ||
export async function hashPassword( | ||
password: string, | ||
rounds: number, | ||
): Promise<string> { | ||
const salt = await genSalt(rounds); | ||
return await hash(password, salt); | ||
} |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.