-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
[PoC][WIP, not ready for review]feat: add abstractions in authentication module #2445
Changes from 2 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 |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import {UserProfile, Credentials, AuthenticatedUser} from '../types'; | ||
import {Entity} from '@loopback/repository'; | ||
export interface AuthenticationServices { | ||
authenticateUser<U extends Entity>( | ||
credentials: Credentials, | ||
): Promise<AuthenticatedUser<U>>; | ||
comparePassword<T = string>(credentialPass: T, userPass: T): Promise<boolean>; | ||
generateAccessToken(user: UserProfile): Promise<string>; | ||
decodeAccessToken(token: string): Promise<UserProfile | undefined>; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './authentication.service'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import {inject} from '@loopback/core'; | ||
import {AuthenticationBindings} from '../keys'; | ||
import {AuthenticationServices} from '../services'; | ||
import {UserProfile, AuthenticatedUser, Credentials} from '../types'; | ||
import {Entity} from '@loopback/repository'; | ||
import {HttpErrors} from '@loopback/rest'; | ||
import {toJSON} from '@loopback/testlab'; | ||
import * as _ from 'lodash'; | ||
|
||
/** | ||
* An interface describes the common authentication strategy. | ||
* | ||
* An authentication strategy is usually a class with an | ||
* authenticate method that verifies a user's identity and | ||
* returns the corresponding user profile. | ||
* | ||
* Please note this file should be moved to @loopback/authentication | ||
*/ | ||
export abstract class AuthenticationStrategy { | ||
constructor( | ||
@inject(AuthenticationBindings.SERVICES) | ||
private services: AuthenticationServices, | ||
) {} | ||
abstract async authenticateRequest( | ||
request: Request, | ||
): Promise<UserProfile | undefined>; | ||
|
||
async authenticateUser<U extends Entity>( | ||
credentials: Credentials, | ||
): Promise<AuthenticatedUser<U>> { | ||
return this.services.authenticateUser(credentials); | ||
} | ||
|
||
async comparePassword<T = string>( | ||
credentialPass: T, | ||
userPass: T, | ||
): Promise<boolean> { | ||
return this.services.comparePassword(credentialPass, userPass); | ||
} | ||
|
||
async generateAccessToken(user: UserProfile): Promise<string> { | ||
return this.services.generateAccessToken(user); | ||
} | ||
|
||
async decodeAccessToken(token: string): Promise<UserProfile | undefined> { | ||
return this.services.decodeAccessToken(token); | ||
} | ||
|
||
async getAccessTokenForUser(credentials: Credentials): Promise<string> { | ||
const user = await this.authenticateUser(credentials); | ||
// There is no guarantee that an Entity contains field `password` | ||
const userWithPassword = Object.assign({password: ''}, user); | ||
const passwordMatched = await this.comparePassword( | ||
credentials.password, | ||
userWithPassword.password, | ||
); | ||
if (!passwordMatched) { | ||
throw new HttpErrors.Unauthorized('The credentials are not correct.'); | ||
} | ||
|
||
const userProfile = _.pick(toJSON(user), ['id', 'email', 'firstName']); | ||
const token = await this.generateAccessToken(userProfile); | ||
return token; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './authentication-strategy'; | ||
export * from './passport/passport-strategy-adapter'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ | |
// License text available at https://opensource.org/licenses/MIT | ||
|
||
import {Request} from '@loopback/rest'; | ||
import {Entity} from '@loopback/repository'; | ||
|
||
/** | ||
* interface definition of a function which accepts a request | ||
|
@@ -22,3 +23,13 @@ export interface UserProfile { | |
name?: string; | ||
email?: string; | ||
} | ||
|
||
export type Credentials = { | ||
email: string; | ||
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. What if the credentials include telephone number or username? 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.
#2246 - to be addressed. 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. @dreamdevil00 Good point. And #2246 is supposed to provide a solution for customizing the fields. (Thanks @dougal83 ) |
||
password: string; | ||
}; | ||
|
||
export type AuthenticatedUser<U extends Entity> = { | ||
authenticated: boolean; | ||
userInfo?: U; | ||
}; |
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 thought
@loopback/testlab
is supposed to be used in testsThere 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.
Hi @shazha thank you for catching it. This PR is still in progress, code changes everyday :( I will let you know when it's ready for review. Thanks for understanding.