From e7b597a7c0ef4cf8178fefe6d65ac1e14995c5d0 Mon Sep 17 00:00:00 2001 From: jannyHou Date: Thu, 21 Feb 2019 13:18:29 -0500 Subject: [PATCH 1/3] feat: add abstractions in authentication module Signed-off-by: jannyHou --- .../src/services/authentication.service.ts | 0 packages/authentication/src/types.ts | 29 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 packages/authentication/src/services/authentication.service.ts diff --git a/packages/authentication/src/services/authentication.service.ts b/packages/authentication/src/services/authentication.service.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/authentication/src/types.ts b/packages/authentication/src/types.ts index 12a170ce3d29..5f0d84d52c57 100644 --- a/packages/authentication/src/types.ts +++ b/packages/authentication/src/types.ts @@ -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,31 @@ export interface UserProfile { name?: string; email?: string; } + +export type Credentials = { + email: string; + password: string; +}; + +export type AuthenticatedUser = { + authenticated: boolean; + userInfo?: U; +}; + +/** + * 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 interface AuthenticationStrategy { + authenticateRequest(request: Request): Promise; + authenticateUser( + credentials: Credentials, + ): Promise>; + generateAccesstoken(user: UserProfile): Promise; + decodeAccesstoken(token: string): Promise; +} From 1da4c51c9dac32f3623e7ffb68930de13c4ac5ab Mon Sep 17 00:00:00 2001 From: jannyHou Date: Fri, 22 Feb 2019 14:45:20 -0500 Subject: [PATCH 2/3] fixup!: add abstractions --- packages/authentication/package.json | 1 + packages/authentication/src/keys.ts | 8 ++- .../passport/auth-action.provider.ts} | 6 +- .../authentication/src/providers/index.ts | 2 +- .../src/services/authentication.service.ts | 10 +++ packages/authentication/src/services/index.ts | 1 + .../src/strategies/authentication-strategy.ts | 65 +++++++++++++++++++ .../authentication/src/strategies/index.ts | 2 + .../passport/passport-strategy-adapter.ts} | 2 +- packages/authentication/src/types.ts | 18 ----- 10 files changed, 90 insertions(+), 25 deletions(-) rename packages/authentication/src/providers/{authentication.provider.ts => auth-action/passport/auth-action.provider.ts} (90%) create mode 100644 packages/authentication/src/services/index.ts create mode 100644 packages/authentication/src/strategies/authentication-strategy.ts create mode 100644 packages/authentication/src/strategies/index.ts rename packages/authentication/src/{strategy-adapter.ts => strategies/passport/passport-strategy-adapter.ts} (98%) diff --git a/packages/authentication/package.json b/packages/authentication/package.json index 0141f5dc6c7b..6df2304a7c4a 100644 --- a/packages/authentication/package.json +++ b/packages/authentication/package.json @@ -25,6 +25,7 @@ "@loopback/metadata": "^1.0.6", "@loopback/openapi-v3": "^1.2.1", "@loopback/rest": "^1.5.5", + "lodash": "^4.17.11", "passport": "^0.4.0", "passport-strategy": "^1.0.0" }, diff --git a/packages/authentication/src/keys.ts b/packages/authentication/src/keys.ts index f878fe9ed1bd..6367a9849ad6 100644 --- a/packages/authentication/src/keys.ts +++ b/packages/authentication/src/keys.ts @@ -8,11 +8,15 @@ import {AuthenticateFn, UserProfile} from './types'; import {AuthenticationMetadata} from './decorators'; import {BindingKey} from '@loopback/context'; import {MetadataAccessor} from '@loopback/metadata'; +import {AuthenticationServices} from './services/authentication.service'; /** * Binding keys used by this component. */ export namespace AuthenticationBindings { + export const SERVICES = BindingKey.create( + 'authentication.services', + ); /** * Key used to bind an authentication strategy to the context for the * authentication function to use. @@ -23,8 +27,8 @@ export namespace AuthenticationBindings { * .toProvider(MyPassportStrategyProvider); * ``` */ - export const STRATEGY = BindingKey.create( - 'authentication.strategy', + export const STRATEGY_RESOLVER = BindingKey.create( + 'authentication.strategy.resolver', ); /** diff --git a/packages/authentication/src/providers/authentication.provider.ts b/packages/authentication/src/providers/auth-action/passport/auth-action.provider.ts similarity index 90% rename from packages/authentication/src/providers/authentication.provider.ts rename to packages/authentication/src/providers/auth-action/passport/auth-action.provider.ts index 880f8a8f89dc..7b01bcf5f14c 100644 --- a/packages/authentication/src/providers/authentication.provider.ts +++ b/packages/authentication/src/providers/auth-action/passport/auth-action.provider.ts @@ -6,9 +6,9 @@ import {Getter, Provider, Setter, inject} from '@loopback/context'; import {Request} from '@loopback/rest'; import {Strategy} from 'passport'; -import {AuthenticationBindings} from '../keys'; -import {StrategyAdapter} from '../strategy-adapter'; -import {AuthenticateFn, UserProfile} from '../types'; +import {AuthenticationBindings} from '../../../keys'; +import {StrategyAdapter} from '../../../strategies/passport/passport-strategy-adapter'; +import {AuthenticateFn, UserProfile} from '../../../types'; /** * @description Provider of a function which authenticates diff --git a/packages/authentication/src/providers/index.ts b/packages/authentication/src/providers/index.ts index 9d6153b57154..290c0e5426ad 100644 --- a/packages/authentication/src/providers/index.ts +++ b/packages/authentication/src/providers/index.ts @@ -4,4 +4,4 @@ // License text available at https://opensource.org/licenses/MIT export * from './auth-metadata.provider'; -export * from './authentication.provider'; +export * from './auth-action/passport/auth-action.provider'; diff --git a/packages/authentication/src/services/authentication.service.ts b/packages/authentication/src/services/authentication.service.ts index e69de29bb2d1..686f83d6d82c 100644 --- a/packages/authentication/src/services/authentication.service.ts +++ b/packages/authentication/src/services/authentication.service.ts @@ -0,0 +1,10 @@ +import {UserProfile, Credentials, AuthenticatedUser} from '../types'; +import {Entity} from '@loopback/repository'; +export interface AuthenticationServices { + authenticateUser( + credentials: Credentials, + ): Promise>; + comparePassword(credentialPass: T, userPass: T): Promise; + generateAccessToken(user: UserProfile): Promise; + decodeAccessToken(token: string): Promise; +} diff --git a/packages/authentication/src/services/index.ts b/packages/authentication/src/services/index.ts new file mode 100644 index 000000000000..f6d76d5e9f46 --- /dev/null +++ b/packages/authentication/src/services/index.ts @@ -0,0 +1 @@ +export * from './authentication.service'; diff --git a/packages/authentication/src/strategies/authentication-strategy.ts b/packages/authentication/src/strategies/authentication-strategy.ts new file mode 100644 index 000000000000..fa27f9820206 --- /dev/null +++ b/packages/authentication/src/strategies/authentication-strategy.ts @@ -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; + + async authenticateUser( + credentials: Credentials, + ): Promise> { + return this.services.authenticateUser(credentials); + } + + async comparePassword( + credentialPass: T, + userPass: T, + ): Promise { + return this.services.comparePassword(credentialPass, userPass); + } + + async generateAccessToken(user: UserProfile): Promise { + return this.services.generateAccessToken(user); + } + + async decodeAccessToken(token: string): Promise { + return this.services.decodeAccessToken(token); + } + + async getAccessTokenForUser(credentials: Credentials): Promise { + 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; + } +} diff --git a/packages/authentication/src/strategies/index.ts b/packages/authentication/src/strategies/index.ts new file mode 100644 index 000000000000..8c73483c8206 --- /dev/null +++ b/packages/authentication/src/strategies/index.ts @@ -0,0 +1,2 @@ +export * from './authentication-strategy'; +export * from './passport/passport-strategy-adapter'; diff --git a/packages/authentication/src/strategy-adapter.ts b/packages/authentication/src/strategies/passport/passport-strategy-adapter.ts similarity index 98% rename from packages/authentication/src/strategy-adapter.ts rename to packages/authentication/src/strategies/passport/passport-strategy-adapter.ts index 1747db7cab24..5998ba687ac3 100644 --- a/packages/authentication/src/strategy-adapter.ts +++ b/packages/authentication/src/strategies/passport/passport-strategy-adapter.ts @@ -5,7 +5,7 @@ import {HttpErrors, Request} from '@loopback/rest'; import {Strategy} from 'passport'; -import {UserProfile} from './types'; +import {UserProfile} from '../../types'; const passportRequestMixin = require('passport/lib/http/request'); diff --git a/packages/authentication/src/types.ts b/packages/authentication/src/types.ts index 5f0d84d52c57..f8524fb3b637 100644 --- a/packages/authentication/src/types.ts +++ b/packages/authentication/src/types.ts @@ -33,21 +33,3 @@ export type AuthenticatedUser = { authenticated: boolean; userInfo?: U; }; - -/** - * 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 interface AuthenticationStrategy { - authenticateRequest(request: Request): Promise; - authenticateUser( - credentials: Credentials, - ): Promise>; - generateAccesstoken(user: UserProfile): Promise; - decodeAccesstoken(token: string): Promise; -} From d3dd7ce0c8859f7d9600d29c25eefc217a30abda Mon Sep 17 00:00:00 2001 From: jannyHou Date: Tue, 26 Feb 2019 12:41:59 -0500 Subject: [PATCH 3/3] fixup!: refactor --- .../src/decorators/authenticate.decorator.ts | 9 +- .../src/providers/auth-action.provider.ts | 125 ++++++++++++++++++ .../passport/auth-action.provider.ts | 58 -------- .../src/providers/auth-metadata.provider.ts | 3 +- .../src/providers/strategy-resolver.ts | 31 +++++ .../src/services/extractor.service.ts | 1 + .../src/services/login.service.ts | 28 ++++ .../src/services/token.service.ts | 28 ++++ .../src/strategies/authentication-strategy.ts | 67 ++-------- packages/authentication/src/types.ts | 45 +++++-- 10 files changed, 266 insertions(+), 129 deletions(-) create mode 100644 packages/authentication/src/providers/auth-action.provider.ts delete mode 100644 packages/authentication/src/providers/auth-action/passport/auth-action.provider.ts create mode 100644 packages/authentication/src/providers/strategy-resolver.ts create mode 100644 packages/authentication/src/services/extractor.service.ts create mode 100644 packages/authentication/src/services/login.service.ts create mode 100644 packages/authentication/src/services/token.service.ts diff --git a/packages/authentication/src/decorators/authenticate.decorator.ts b/packages/authentication/src/decorators/authenticate.decorator.ts index 5546b6f71fad..d71c413a931c 100644 --- a/packages/authentication/src/decorators/authenticate.decorator.ts +++ b/packages/authentication/src/decorators/authenticate.decorator.ts @@ -9,14 +9,7 @@ import { MethodDecoratorFactory, } from '@loopback/context'; import {AUTHENTICATION_METADATA_KEY} from '../keys'; - -/** - * Authentication metadata stored via Reflection API - */ -export interface AuthenticationMetadata { - strategy: string; - options?: Object; -} +import {AuthenticationMetadata} from '..'; /** * Mark a controller method as requiring authenticated user. diff --git a/packages/authentication/src/providers/auth-action.provider.ts b/packages/authentication/src/providers/auth-action.provider.ts new file mode 100644 index 000000000000..83376aee5543 --- /dev/null +++ b/packages/authentication/src/providers/auth-action.provider.ts @@ -0,0 +1,125 @@ +// Copyright IBM Corp. 2017,2018. All Rights Reserved. +// Node module: @loopback/authentication +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {Getter, Provider, Setter, inject} from '@loopback/context'; +import {Request} from '@loopback/rest'; +import {AuthenticationBindings} from '../keys'; +import {ActionType, AuthenticationMetadata} from '../types'; +import {Model} from '@loopback/repository'; +import {AuthStrategy} from '../strategies'; + +/** + * @description Provider of a function which authenticates + * @example `context.bind('authentication_key') + * .toProvider(AuthenticateActionProvider)` + */ +export class AuthenticateActionProvider + implements Provider { + constructor( + // The provider is instantiated for Sequence constructor, + // at which time we don't have information about the current + // route yet. This information is needed to determine + // what auth strategy should be used. + // To solve this, we are injecting a getter function that will + // defer resolution of the strategy until authenticate() action + // is executed. + @inject.getter(AuthenticationBindings.STRATEGY_RESOLVER) + readonly getStrategy: Getter>, + @inject(AuthenticationBindings.METADATA) + private metadata: AuthenticationMetadata, + // what if the user is not set in the action + @inject.setter(AuthenticationBindings.CURRENT_USER) + readonly setCurrentUser: Setter, + ) {} + + /** + * @returns authenticateFn + */ + value(): Function { + const actionName = this.getActionName(); + + // A workaround for + // return (request: Request) => this[actionName].call(request) + // got error for ^ and solving it. + switch (actionName) { + case 'verify': + return (request: Request) => this.verify(request); + case 'login': + return (request: Request) => this.login(request); + case 'register': + return (request: Request) => this.register(request); + default: + // tslint:disable-next-line:no-unused + return (request: Request) => { + return; + }; + } + } + + /** + * Get the name of authentication action to perform from + * an endpoint's metadata + */ + getActionName(): ActionType | undefined { + if (!this.metadata || !this.metadata.action) { + return; + } + return this.metadata.action; + } + + /** + * The implementation of authenticate() sequence action(verify). + * @param request The incoming request provided by the REST layer + */ + async verify(request: Request): Promise { + const strategy = await this.getStrategy(); + if (!strategy) { + // The invoked operation does not require authentication. + return undefined; + } + if (!strategy.verify) { + throw new Error('invalid strategy parameter'); + } + + const user = await strategy.verify(request); + + if (user) this.setCurrentUser(user); + return user; + } + + /** + * The implementation of authenticate() sequence action(login). + * @param request The incoming request provided by the REST layer + */ + async login(request: Request): Promise { + const strategy = await this.getStrategy(); + if (!strategy) { + // The invoked operation does not require authentication. + return undefined; + } + if (!strategy.login) { + throw new Error('invalid strategy parameter'); + } + + return await strategy.login(request); + } + + /** + * The implementation of authenticate() sequence action(register). + * @param request The incoming request provided by the REST layer + */ + async register(request: Request): Promise { + const strategy = await this.getStrategy(); + if (!strategy) { + // The invoked operation does not require authentication. + return undefined; + } + if (!strategy.register) { + throw new Error('invalid strategy parameter'); + } + + return await strategy.register(request); + } +} diff --git a/packages/authentication/src/providers/auth-action/passport/auth-action.provider.ts b/packages/authentication/src/providers/auth-action/passport/auth-action.provider.ts deleted file mode 100644 index 7b01bcf5f14c..000000000000 --- a/packages/authentication/src/providers/auth-action/passport/auth-action.provider.ts +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright IBM Corp. 2017,2018. All Rights Reserved. -// Node module: @loopback/authentication -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -import {Getter, Provider, Setter, inject} from '@loopback/context'; -import {Request} from '@loopback/rest'; -import {Strategy} from 'passport'; -import {AuthenticationBindings} from '../../../keys'; -import {StrategyAdapter} from '../../../strategies/passport/passport-strategy-adapter'; -import {AuthenticateFn, UserProfile} from '../../../types'; - -/** - * @description Provider of a function which authenticates - * @example `context.bind('authentication_key') - * .toProvider(AuthenticateActionProvider)` - */ -export class AuthenticateActionProvider implements Provider { - constructor( - // The provider is instantiated for Sequence constructor, - // at which time we don't have information about the current - // route yet. This information is needed to determine - // what auth strategy should be used. - // To solve this, we are injecting a getter function that will - // defer resolution of the strategy until authenticate() action - // is executed. - @inject.getter(AuthenticationBindings.STRATEGY) - readonly getStrategy: Getter, - @inject.setter(AuthenticationBindings.CURRENT_USER) - readonly setCurrentUser: Setter, - ) {} - - /** - * @returns authenticateFn - */ - value(): AuthenticateFn { - return request => this.action(request); - } - - /** - * The implementation of authenticate() sequence action. - * @param request The incoming request provided by the REST layer - */ - async action(request: Request): Promise { - const strategy = await this.getStrategy(); - if (!strategy) { - // The invoked operation does not require authentication. - return undefined; - } - if (!strategy.authenticate) { - throw new Error('invalid strategy parameter'); - } - const strategyAdapter = new StrategyAdapter(strategy); - const user = await strategyAdapter.authenticate(request); - this.setCurrentUser(user); - return user; - } -} diff --git a/packages/authentication/src/providers/auth-metadata.provider.ts b/packages/authentication/src/providers/auth-metadata.provider.ts index cdf5bad4a268..ea2e6074d21d 100644 --- a/packages/authentication/src/providers/auth-metadata.provider.ts +++ b/packages/authentication/src/providers/auth-metadata.provider.ts @@ -5,7 +5,8 @@ import {CoreBindings} from '@loopback/core'; import {Constructor, Provider, inject} from '@loopback/context'; -import {AuthenticationMetadata, getAuthenticateMetadata} from '../decorators'; +import {getAuthenticateMetadata} from '../decorators'; +import {AuthenticationMetadata} from '../'; /** * @description Provides authentication metadata of a controller method diff --git a/packages/authentication/src/providers/strategy-resolver.ts b/packages/authentication/src/providers/strategy-resolver.ts new file mode 100644 index 000000000000..ad293306684f --- /dev/null +++ b/packages/authentication/src/providers/strategy-resolver.ts @@ -0,0 +1,31 @@ +import {Provider, inject, ValueOrPromise} from '@loopback/context'; +import {AuthStrategy} from '../strategies'; +import {AuthenticationBindings, AuthenticationMetadata} from '../..'; +import * as _ from 'lodash'; + +export class StrategyResolverProvider + implements Provider { + constructor( + @inject(AuthenticationBindings.METADATA) + private metadata: AuthenticationMetadata, + ) {} + + value(): ValueOrPromise { + // tslint:disable-next-line:no-unused + const strategyName = this.metadata && this.metadata.strategy; + + // Extension point + // Responsibility of the extension point: + // 1. return corresponding strategy if found + // 2. throw error if strategy is not available + // switch (name) { + // case 'jwt': + // return JWTStrategy(); + // case 'openid': + // return OpenidStrategy(); + // default: + // return; + // } + return; + } +} diff --git a/packages/authentication/src/services/extractor.service.ts b/packages/authentication/src/services/extractor.service.ts new file mode 100644 index 000000000000..516a613b6cd9 --- /dev/null +++ b/packages/authentication/src/services/extractor.service.ts @@ -0,0 +1 @@ +// To be created, see the discussion in login and token service. diff --git a/packages/authentication/src/services/login.service.ts b/packages/authentication/src/services/login.service.ts new file mode 100644 index 000000000000..78928bbafcce --- /dev/null +++ b/packages/authentication/src/services/login.service.ts @@ -0,0 +1,28 @@ +import {Credentials} from '../types'; +import {Request} from '@loopback/rest'; + +/** + * A service that provide login operations. + * + * Discussion: + * 1. should we turn the following 2 functions into standalone interfaces + * so that each service itself could be an extension point + * 2. should we use a generic type for Credentials instead of hardcode it? + */ +export interface LoginService { + /** + * Extract credentials like `username` and `password` from incoming request + * Discussion: + * 1. should we move extractors into a separate extractor service? + * 2. should controller execute extractCredentials or auth action does it? + * Or it's on user's choice? + * 3. should we specify `Request` from the rest module as the request type? + * @param request The incoming HTTP request + */ + extractCredentials(request: Request): Promise; + /** + * Verify the credential maps to a valid user and return the user if found + * @param credentials the credentials returned by method `extractCredentials` + */ + verifyCredentials(credentials: Credentials): Promise; +} diff --git a/packages/authentication/src/services/token.service.ts b/packages/authentication/src/services/token.service.ts new file mode 100644 index 000000000000..e686c8d7a6b5 --- /dev/null +++ b/packages/authentication/src/services/token.service.ts @@ -0,0 +1,28 @@ +import {Response} from '@loopback/rest'; +import {Model} from '@loopback/repository'; + +/** + * A service that provide access token operations. + */ +export interface TokenService { + /** + * Generate the access token for a given user + * Consumed by login action + * @param user + * @param options + */ + generateAccessToken(user: U, options: Object): Promise; + /** + * Write the token to HTTP response + * Consumed by login action + * @param response The HTTP response to return + */ + serializeAccessToken(response: Response): Promise; + /** + * Discussion: + * 1. should we move extractors into a separate extractor service? + * Extract the access token from request, it could be from header or cookie + * @param request The incoming HTTP request + */ + extractAccessToken(request: Request): Promise; +} diff --git a/packages/authentication/src/strategies/authentication-strategy.ts b/packages/authentication/src/strategies/authentication-strategy.ts index fa27f9820206..baf7bf2d6a68 100644 --- a/packages/authentication/src/strategies/authentication-strategy.ts +++ b/packages/authentication/src/strategies/authentication-strategy.ts @@ -1,65 +1,24 @@ -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'; +import {Request} from '@loopback/rest'; +import {Model} from '@loopback/repository'; /** - * An interface describes the common authentication strategy. + * An interface describes a typical 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; - - async authenticateUser( - credentials: Credentials, - ): Promise> { - return this.services.authenticateUser(credentials); - } - - async comparePassword( - credentialPass: T, - userPass: T, - ): Promise { - return this.services.comparePassword(credentialPass, userPass); - } - - async generateAccessToken(user: UserProfile): Promise { - return this.services.generateAccessToken(user); - } - async decodeAccessToken(token: string): Promise { - return this.services.decodeAccessToken(token); - } +export interface AuthStrategy { + verify(request: Request): Promise; - async getAccessTokenForUser(credentials: Credentials): Promise { - 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.'); - } + register(request: Request): Promise; - const userProfile = _.pick(toJSON(user), ['id', 'email', 'firstName']); - const token = await this.generateAccessToken(userProfile); - return token; - } + /** + * Discussion: + * 1. how do we decide what's the return data of login operation? + * it could be an access token, or a user, or something else. + * @param request + */ + login(request: Request): Promise; } diff --git a/packages/authentication/src/types.ts b/packages/authentication/src/types.ts index f8524fb3b637..cbac49f8be89 100644 --- a/packages/authentication/src/types.ts +++ b/packages/authentication/src/types.ts @@ -4,15 +4,15 @@ // License text available at https://opensource.org/licenses/MIT import {Request} from '@loopback/rest'; -import {Entity} from '@loopback/repository'; +import {Entity, Model} from '@loopback/repository'; -/** - * interface definition of a function which accepts a request - * and returns an authenticated user - */ -export interface AuthenticateFn { - (request: Request): Promise; -} +// /** +// * interface definition of a function which accepts a request +// * and returns an authenticated user +// */ +// export interface AuthenticateFn { +// (request: Request): Promise; +// } /** * interface definition of a user profile @@ -33,3 +33,32 @@ export type AuthenticatedUser = { authenticated: boolean; userInfo?: U; }; + +/** + * Some user scenarios to consider + * + * - If + * + * @example + * @authenticate({ + * strategy: 'JWT', + * extractor: 'header', + * action: 'verify' + * }) + * async findOrdersForUser() { + * const user = this.currentUser; + * return await userRepo.orders(user.id); + * } + */ +export type AuthenticationMetadata = { + action?: ActionType; + strategy?: string; + extractor?: string; + options: Object; +}; + +/** + * The action from a particular strategy that an endpoint + * wants to perform. + */ +export type ActionType = 'register' | 'login' | 'verify';