From 657b89d2a8e891a05109c0947236bf00d2f98764 Mon Sep 17 00:00:00 2001 From: nflaig Date: Sat, 11 Jul 2020 22:51:08 +0200 Subject: [PATCH] feat(authentication): update signature of authenticate decorator It is now possible to provide multiple strategy names and/or metadata objects BREAKING CHANGE: The `@authenticate` signature changed, options are no longer a separate input parameter but instead have to be provided in the metadata object. The metadata value is now `AuthenticationMetadata[]`. Signed-off-by: nflaig --- .../decorators/authenticate.decorator.unit.ts | 78 +++++++++++++------ .../__tests__/unit/fixtures/mock-metadata.ts | 2 +- .../providers/auth-metadata.provider.unit.ts | 64 ++++++++------- .../providers/auth-strategy.provider.unit.ts | 21 +++-- ...thentication-metadata-for-strategy.unit.ts | 45 +++++++++++ .../src/decorators/authenticate.decorator.ts | 49 ++++++------ packages/authentication/src/keys.ts | 17 ++-- .../src/providers/auth-action.provider.ts | 8 +- .../src/providers/auth-metadata.provider.ts | 8 +- .../src/providers/auth-strategy.provider.ts | 19 ++--- packages/authentication/src/types.ts | 19 ++++- 11 files changed, 210 insertions(+), 120 deletions(-) create mode 100644 packages/authentication/src/__tests__/unit/types/authentication-metadata-for-strategy.unit.ts diff --git a/packages/authentication/src/__tests__/unit/decorators/authenticate.decorator.unit.ts b/packages/authentication/src/__tests__/unit/decorators/authenticate.decorator.unit.ts index 5bdbcd871fa1..1a6bdd4c1878 100644 --- a/packages/authentication/src/__tests__/unit/decorators/authenticate.decorator.unit.ts +++ b/packages/authentication/src/__tests__/unit/decorators/authenticate.decorator.unit.ts @@ -8,16 +8,15 @@ import {authenticate, getAuthenticateMetadata} from '../../..'; describe('Authentication', () => { describe('@authenticate decorator', () => { - it('can add authenticate metadata to target method with options', () => { + it('can add authenticate metadata to target method', () => { class TestClass { - @authenticate('my-strategy', {option1: 'value1', option2: 'value2'}) + @authenticate('my-strategy') whoAmI() {} } const metaData = getAuthenticateMetadata(TestClass, 'whoAmI'); - expect(metaData).to.eql({ + expect(metaData?.[0]).to.eql({ strategy: 'my-strategy', - options: {option1: 'value1', option2: 'value2'}, }); }); @@ -31,7 +30,7 @@ describe('Authentication', () => { } const metaData = getAuthenticateMetadata(TestClass, 'whoAmI'); - expect(metaData).to.eql({ + expect(metaData?.[0]).to.eql({ strategy: 'my-strategy', options: {option1: 'value1', option2: 'value2'}, }); @@ -44,47 +43,82 @@ describe('Authentication', () => { } const metaData = getAuthenticateMetadata(TestClass, 'whoAmI'); - expect(metaData).to.eql({strategy: 'my-strategy', options: {}}); + expect(metaData?.[0]).to.eql({ + strategy: 'my-strategy', + }); }); - it('can add authenticate metadata to target method with strategies as array', () => { + it('can add authenticate metadata to target method with multiple strategies', () => { class TestClass { - @authenticate(['my-strategy', 'my-strategy2']) + @authenticate('my-strategy', 'my-strategy2') whoAmI() {} } const metaData = getAuthenticateMetadata(TestClass, 'whoAmI'); - expect(metaData).to.eql({ - strategy: ['my-strategy', 'my-strategy2'], - options: {}, + expect(metaData?.[0]).to.eql({ + strategy: 'my-strategy', + }); + expect(metaData?.[1]).to.eql({ + strategy: 'my-strategy2', }); }); - it('adds authenticate metadata to target class', () => { - @authenticate('my-strategy', {option1: 'value1', option2: 'value2'}) + it('can add authenticate metadata to target method with multiple objects', () => { class TestClass { + @authenticate( + { + strategy: 'my-strategy', + options: {option1: 'value1', option2: 'value2'}, + }, + { + strategy: 'my-strategy2', + options: {option1: 'value1', option2: 'value2'}, + }, + ) whoAmI() {} } const metaData = getAuthenticateMetadata(TestClass, 'whoAmI'); - expect(metaData).to.eql({ + expect(metaData?.[0]).to.eql({ strategy: 'my-strategy', options: {option1: 'value1', option2: 'value2'}, }); + expect(metaData?.[1]).to.eql({ + strategy: 'my-strategy2', + options: {option1: 'value1', option2: 'value2'}, + }); + }); + + it('adds authenticate metadata to target class', () => { + @authenticate('my-strategy') + class TestClass { + whoAmI() {} + } + + const metaData = getAuthenticateMetadata(TestClass, 'whoAmI'); + expect(metaData?.[0]).to.eql({ + strategy: 'my-strategy', + }); }); it('overrides class level metadata by method level', () => { - @authenticate('my-strategy', {option1: 'value1', option2: 'value2'}) + @authenticate({ + strategy: 'my-strategy', + options: {option1: 'value1', option2: 'value2'}, + }) class TestClass { - @authenticate('another-strategy', { - option1: 'valueA', - option2: 'value2', + @authenticate({ + strategy: 'another-strategy', + options: { + option1: 'valueA', + option2: 'value2', + }, }) whoAmI() {} } const metaData = getAuthenticateMetadata(TestClass, 'whoAmI'); - expect(metaData).to.eql({ + expect(metaData?.[0]).to.eql({ strategy: 'another-strategy', options: {option1: 'valueA', option2: 'value2'}, }); @@ -92,14 +126,14 @@ describe('Authentication', () => { }); it('can skip authentication', () => { - @authenticate('my-strategy', {option1: 'value1', option2: 'value2'}) + @authenticate('my-strategy') class TestClass { @authenticate.skip() whoAmI() {} } const metaData = getAuthenticateMetadata(TestClass, 'whoAmI'); - expect(metaData).to.containEql({skip: true}); + expect(metaData?.[0]).to.containEql({skip: true}); }); it('can skip authentication at class level', () => { @@ -109,6 +143,6 @@ describe('Authentication', () => { } const metaData = getAuthenticateMetadata(TestClass, 'whoAmI'); - expect(metaData).to.containEql({skip: true}); + expect(metaData?.[0]).to.containEql({skip: true}); }); }); diff --git a/packages/authentication/src/__tests__/unit/fixtures/mock-metadata.ts b/packages/authentication/src/__tests__/unit/fixtures/mock-metadata.ts index c45dd186f625..8e35a64e76f4 100644 --- a/packages/authentication/src/__tests__/unit/fixtures/mock-metadata.ts +++ b/packages/authentication/src/__tests__/unit/fixtures/mock-metadata.ts @@ -8,6 +8,6 @@ export const mockAuthenticationMetadata: AuthenticationMetadata = { }; export const mockAuthenticationMetadata2: AuthenticationMetadata = { - strategy: ['MockStrategy', 'MockStrategy2'], + strategy: 'MockStrategy2', options, }; diff --git a/packages/authentication/src/__tests__/unit/providers/auth-metadata.provider.unit.ts b/packages/authentication/src/__tests__/unit/providers/auth-metadata.provider.unit.ts index b56bedf037b2..3524396ae4db 100644 --- a/packages/authentication/src/__tests__/unit/providers/auth-metadata.provider.unit.ts +++ b/packages/authentication/src/__tests__/unit/providers/auth-metadata.provider.unit.ts @@ -3,17 +3,17 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {Context, Provider, CoreBindings} from '@loopback/core'; +import {Context, CoreBindings, Provider} from '@loopback/core'; import {expect} from '@loopback/testlab'; import {authenticate, AuthenticationMetadata} from '../../..'; import {AuthenticationBindings} from '../../../keys'; import {AuthMetadataProvider} from '../../../providers'; describe('AuthMetadataProvider', () => { - let provider: Provider; + let provider: Provider; class TestController { - @authenticate('my-strategy', {option1: 'value1', option2: 'value2'}) + @authenticate('my-strategy') whoAmI() {} @authenticate.skip() @@ -29,11 +29,10 @@ describe('AuthMetadataProvider', () => { describe('value()', () => { it('returns the auth metadata of a controller method', async () => { const authMetadata: - | AuthenticationMetadata + | AuthenticationMetadata[] | undefined = await provider.value(); - expect(authMetadata).to.be.eql({ + expect(authMetadata?.[0]).to.be.eql({ strategy: 'my-strategy', - options: {option1: 'value1', option2: 'value2'}, }); }); @@ -45,12 +44,11 @@ describe('AuthMetadataProvider', () => { context .bind(CoreBindings.CONTROLLER_METHOD_META) .toProvider(AuthMetadataProvider); - const authMetadata = await context.get( - CoreBindings.CONTROLLER_METHOD_META, - ); - expect(authMetadata).to.be.eql({ + const authMetadata: + | AuthenticationMetadata[] + | undefined = await context.get(CoreBindings.CONTROLLER_METHOD_META); + expect(authMetadata?.[0]).to.be.eql({ strategy: 'my-strategy', - options: {option1: 'value1', option2: 'value2'}, }); }); @@ -61,10 +59,10 @@ describe('AuthMetadataProvider', () => { context .bind(CoreBindings.CONTROLLER_METHOD_META) .toProvider(AuthMetadataProvider); - const authMetadata = await context.get( - CoreBindings.CONTROLLER_METHOD_META, - ); - expect(authMetadata).to.be.undefined(); + const authMetadata: + | AuthenticationMetadata[] + | undefined = await context.get(CoreBindings.CONTROLLER_METHOD_META); + expect(authMetadata?.[0]).to.be.undefined(); }); it('returns undefined for a method decorated with @authenticate.skip even with default metadata', async () => { @@ -76,11 +74,11 @@ describe('AuthMetadataProvider', () => { .toProvider(AuthMetadataProvider); context .configure(AuthenticationBindings.COMPONENT) - .to({defaultMetadata: {strategy: 'xyz'}}); - const authMetadata = await context.get( - CoreBindings.CONTROLLER_METHOD_META, - ); - expect(authMetadata).to.be.undefined(); + .to({defaultMetadata: [{strategy: 'xyz'}]}); + const authMetadata: + | AuthenticationMetadata[] + | undefined = await context.get(CoreBindings.CONTROLLER_METHOD_META); + expect(authMetadata?.[0]).to.be.undefined(); }); it('returns undefined if no auth metadata is defined', async () => { @@ -92,10 +90,10 @@ describe('AuthMetadataProvider', () => { context .bind(CoreBindings.CONTROLLER_METHOD_META) .toProvider(AuthMetadataProvider); - const authMetadata = await context.get( - CoreBindings.CONTROLLER_METHOD_META, - ); - expect(authMetadata).to.be.undefined(); + const authMetadata: + | AuthenticationMetadata[] + | undefined = await context.get(CoreBindings.CONTROLLER_METHOD_META); + expect(authMetadata?.[0]).to.be.undefined(); }); it('returns default metadata if no auth metadata is defined', async () => { @@ -106,14 +104,14 @@ describe('AuthMetadataProvider', () => { context.bind(CoreBindings.CONTROLLER_METHOD_NAME).to('whoAmI'); context .configure(AuthenticationBindings.COMPONENT) - .to({defaultMetadata: {strategy: 'xyz'}}); + .to({defaultMetadata: [{strategy: 'xyz'}]}); context .bind(CoreBindings.CONTROLLER_METHOD_META) .toProvider(AuthMetadataProvider); - const authMetadata = await context.get( - CoreBindings.CONTROLLER_METHOD_META, - ); - expect(authMetadata).to.be.eql({strategy: 'xyz'}); + const authMetadata: + | AuthenticationMetadata[] + | undefined = await context.get(CoreBindings.CONTROLLER_METHOD_META); + expect(authMetadata?.[0]).to.be.eql({strategy: 'xyz'}); }); it('returns undefined when the class or method is missing', async () => { @@ -121,10 +119,10 @@ describe('AuthMetadataProvider', () => { context .bind(CoreBindings.CONTROLLER_METHOD_META) .toProvider(AuthMetadataProvider); - const authMetadata = await context.get( - CoreBindings.CONTROLLER_METHOD_META, - ); - expect(authMetadata).to.be.undefined(); + const authMetadata: + | AuthenticationMetadata[] + | undefined = await context.get(CoreBindings.CONTROLLER_METHOD_META); + expect(authMetadata?.[0]).to.be.undefined(); }); }); }); diff --git a/packages/authentication/src/__tests__/unit/providers/auth-strategy.provider.unit.ts b/packages/authentication/src/__tests__/unit/providers/auth-strategy.provider.unit.ts index ef7cdf3fde99..09ed3753e57e 100644 --- a/packages/authentication/src/__tests__/unit/providers/auth-strategy.provider.unit.ts +++ b/packages/authentication/src/__tests__/unit/providers/auth-strategy.provider.unit.ts @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2020. 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 {Context} from '@loopback/core'; import {expect} from '@loopback/testlab'; import { @@ -20,7 +25,7 @@ describe('AuthStrategyProvider', () => { beforeEach(() => { givenAuthenticationStrategyProvider( [mockStrategy, mockStrategy2], - mockAuthenticationMetadata2, + [mockAuthenticationMetadata, mockAuthenticationMetadata2], ); }); @@ -29,20 +34,20 @@ describe('AuthStrategyProvider', () => { const strategies = await strategyProvider.value(); expect(strategies).to.not.be.undefined(); - expect(strategies![0]).to.be.equal(mockStrategy); - expect(strategies![1]).to.be.equal(mockStrategy2); + expect(strategies?.[0]).to.be.equal(mockStrategy); + expect(strategies?.[1]).to.be.equal(mockStrategy2); }); it('should only return the authentication strategy specified in the authentication metadata', async () => { givenAuthenticationStrategyProvider( [mockStrategy, mockStrategy2], - mockAuthenticationMetadata, + [mockAuthenticationMetadata], ); const strategies = await strategyProvider.value(); expect(strategies?.length).to.be.equal(1); - expect(strategies![0]).to.be.equal(mockStrategy); + expect(strategies?.[0]).to.be.equal(mockStrategy); }); it('should return undefined if the authentication metadata is not available', async () => { @@ -54,11 +59,11 @@ describe('AuthStrategyProvider', () => { }); it('should throw an error if the authentication strategy is not available', async () => { - givenAuthenticationStrategyProvider([], mockAuthenticationMetadata); + givenAuthenticationStrategyProvider([], [mockAuthenticationMetadata]); await expect(strategyProvider.value()).to.be.rejected(); - givenAuthenticationStrategyProvider([], mockAuthenticationMetadata2); + givenAuthenticationStrategyProvider([], [mockAuthenticationMetadata2]); await expect(strategyProvider.value()).to.be.rejected(); }); @@ -91,7 +96,7 @@ describe('AuthStrategyProvider', () => { function givenAuthenticationStrategyProvider( strategies: AuthenticationStrategy[], - metadata: AuthenticationMetadata | undefined, + metadata: AuthenticationMetadata[] | undefined, ) { strategyProvider = new AuthenticationStrategyProvider( () => Promise.resolve(strategies), diff --git a/packages/authentication/src/__tests__/unit/types/authentication-metadata-for-strategy.unit.ts b/packages/authentication/src/__tests__/unit/types/authentication-metadata-for-strategy.unit.ts new file mode 100644 index 000000000000..0eceeed55534 --- /dev/null +++ b/packages/authentication/src/__tests__/unit/types/authentication-metadata-for-strategy.unit.ts @@ -0,0 +1,45 @@ +// Copyright IBM Corp. 2020. 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 {expect} from '@loopback/testlab'; +import { + AuthenticationMetadata, + getAuthenticationMetadataForStrategy, +} from '../../../types'; +import { + mockAuthenticationMetadata, + mockAuthenticationMetadata2, +} from '../fixtures/mock-metadata'; + +describe('getAuthenticationMetadataForStrategy', () => { + let metadata: AuthenticationMetadata[]; + + beforeEach(givenAuthenticationMetadata); + + it('should return the authentication metadata for the specified strategy', () => { + const {strategy, options} = mockAuthenticationMetadata; + const strategyMetadata = getAuthenticationMetadataForStrategy( + metadata, + strategy, + ); + + expect(strategyMetadata).to.not.be.undefined(); + expect(strategyMetadata!.strategy).to.equal(strategy); + expect(strategyMetadata!.options).to.equal(options); + }); + + it('should return undefined if no metadata exists for the specified strategy', () => { + const strategyMetadata = getAuthenticationMetadataForStrategy( + metadata, + 'doesnotexist', + ); + + expect(strategyMetadata).to.be.undefined(); + }); + + function givenAuthenticationMetadata() { + metadata = [mockAuthenticationMetadata, mockAuthenticationMetadata2]; + } +}); diff --git a/packages/authentication/src/decorators/authenticate.decorator.ts b/packages/authentication/src/decorators/authenticate.decorator.ts index ee22e518d1df..2718d3ae60bf 100644 --- a/packages/authentication/src/decorators/authenticate.decorator.ts +++ b/packages/authentication/src/decorators/authenticate.decorator.ts @@ -18,19 +18,17 @@ import { import {AuthenticationMetadata} from '../types'; class AuthenticateClassDecoratorFactory extends ClassDecoratorFactory< - AuthenticationMetadata + AuthenticationMetadata[] > {} /** * Mark a controller method as requiring authenticated user. * - * @param strategyNameOrMetadata - The name of the authentication strategy to use - * or the authentication metadata object. - * @param options - Additional options to configure the authentication. + * @param strategies - The names of the authentication strategies to use + * or authentication metadata objects. */ export function authenticate( - strategyNameOrMetadata: string | string[] | AuthenticationMetadata, - options?: object, + ...strategies: (string | AuthenticationMetadata)[] ) { return function authenticateDecoratorForClassOrMethod( // Class or a prototype @@ -42,34 +40,35 @@ export function authenticate( // eslint-disable-next-line @typescript-eslint/no-explicit-any methodDescriptor?: TypedPropertyDescriptor, ) { - let spec: AuthenticationMetadata; - if ( - typeof strategyNameOrMetadata === 'object' && - !Array.isArray(strategyNameOrMetadata) - ) { - spec = strategyNameOrMetadata; - } else { - spec = {strategy: strategyNameOrMetadata, options: options ?? {}}; + const specs: AuthenticationMetadata[] = []; + + for (const strategy of strategies) { + if (typeof strategy === 'object') { + specs.push(strategy); + } else { + specs.push({strategy: strategy}); + } } + if (method && methodDescriptor) { // Method - return MethodDecoratorFactory.createDecorator( + return MethodDecoratorFactory.createDecorator( AUTHENTICATION_METADATA_KEY, - spec, + specs, {decoratorName: '@authenticate'}, )(target, method, methodDescriptor); } if (typeof target === 'function' && !method && !methodDescriptor) { // Class - return AuthenticateClassDecoratorFactory.createDecorator( - AUTHENTICATION_METADATA_CLASS_KEY, - spec, - {decoratorName: '@authenticate'}, - )(target); + return AuthenticateClassDecoratorFactory.createDecorator< + AuthenticationMetadata[] + >(AUTHENTICATION_METADATA_CLASS_KEY, specs, { + decoratorName: '@authenticate', + })(target); } // Not on a class or method throw new Error( - '@intercept cannot be used on a property: ' + + '@authenticate cannot be used on a property: ' + DecoratorFactory.getTargetName(target, method, methodDescriptor), ); }; @@ -91,16 +90,16 @@ export namespace authenticate { export function getAuthenticateMetadata( targetClass: Constructor<{}>, methodName: string, -): AuthenticationMetadata | undefined { +): AuthenticationMetadata[] | undefined { // First check method level - let metadata = MetadataInspector.getMethodMetadata( + let metadata = MetadataInspector.getMethodMetadata( AUTHENTICATION_METADATA_METHOD_KEY, targetClass.prototype, methodName, ); if (metadata) return metadata; // Check if the class level has `@authenticate` - metadata = MetadataInspector.getClassMetadata( + metadata = MetadataInspector.getClassMetadata( AUTHENTICATION_METADATA_CLASS_KEY, targetClass, ); diff --git a/packages/authentication/src/keys.ts b/packages/authentication/src/keys.ts index d9cce882a83f..5f79a8f51920 100644 --- a/packages/authentication/src/keys.ts +++ b/packages/authentication/src/keys.ts @@ -39,14 +39,14 @@ export namespace AuthenticationBindings { >('authentication.userProfileFactory'); /** - * Key used to bind an authentication strategy to the context for the - * authentication function to use. + * Key used to bind an authentication strategy or multiple strategies + * to the context for the authentication function to use. * * @example * ```ts * server * .bind(AuthenticationBindings.STRATEGY) - * .toProvider([MyAuthenticationStrategy]); + * .toProvider(MyAuthenticationStrategy); * ``` */ export const STRATEGY = BindingKey.create< @@ -97,20 +97,19 @@ export namespace AuthenticationBindings { * class MyPassportStrategyProvider implements Provider { * constructor( * @inject(AuthenticationBindings.METADATA) - * private metadata: AuthenticationMetadata, + * private metadata?: AuthenticationMetadata[], * ) {} * value(): ValueOrPromise { - * if (this.metadata) { - * const name = this.metadata.strategy; + * if (this.metadata?.length) { * // logic to determine which authentication strategy to return * } * } * } * ``` */ - export const METADATA = BindingKey.create( - 'authentication.operationMetadata', - ); + export const METADATA = BindingKey.create< + AuthenticationMetadata[] | undefined + >('authentication.operationMetadata'); export const AUTHENTICATION_STRATEGY_EXTENSION_POINT_NAME = 'authentication.strategies'; diff --git a/packages/authentication/src/providers/auth-action.provider.ts b/packages/authentication/src/providers/auth-action.provider.ts index 853c3a31d585..90336ef4a41c 100644 --- a/packages/authentication/src/providers/auth-action.provider.ts +++ b/packages/authentication/src/providers/auth-action.provider.ts @@ -27,7 +27,7 @@ export class AuthenticateActionProvider implements Provider { // is executed. @inject.getter(AuthenticationBindings.STRATEGY) readonly getStrategies: Getter< - AuthenticationStrategy | AuthenticationStrategy[] + AuthenticationStrategy | AuthenticationStrategy[] | undefined >, @inject.setter(SecurityBindings.USER) readonly setCurrentUser: Setter, @@ -54,7 +54,7 @@ export class AuthenticateActionProvider implements Provider { // The invoked operation does not require authentication. return undefined; } - + // convert to array if required strategies = Array.isArray(strategies) ? strategies : [strategies]; let authenticated = false; @@ -92,8 +92,8 @@ export class AuthenticateActionProvider implements Provider { }); throw error; } - } catch (err) { - authError = authError ?? err; + } catch (error) { + authError = authError ?? error; } } diff --git a/packages/authentication/src/providers/auth-metadata.provider.ts b/packages/authentication/src/providers/auth-metadata.provider.ts index 5df7d5d3d6cb..b8861f672996 100644 --- a/packages/authentication/src/providers/auth-metadata.provider.ts +++ b/packages/authentication/src/providers/auth-metadata.provider.ts @@ -6,9 +6,9 @@ import { config, Constructor, + CoreBindings, inject, Provider, - CoreBindings, } from '@loopback/core'; import {getAuthenticateMetadata} from '../decorators'; import {AuthenticationBindings} from '../keys'; @@ -19,7 +19,7 @@ import {AuthenticationMetadata, AuthenticationOptions} from '../types'; * @example `context.bind('authentication.operationMetadata').toProvider(AuthMetadataProvider)` */ export class AuthMetadataProvider - implements Provider { + implements Provider { constructor( @inject(CoreBindings.CONTROLLER_CLASS, {optional: true}) private readonly controllerClass: Constructor<{}>, @@ -32,14 +32,14 @@ export class AuthMetadataProvider /** * @returns AuthenticationMetadata */ - value(): AuthenticationMetadata | undefined { + value(): AuthenticationMetadata[] | undefined { if (!this.controllerClass || !this.methodName) return; const metadata = getAuthenticateMetadata( this.controllerClass, this.methodName, ); // Skip authentication if `skip` is `true` - if (metadata?.skip) return undefined; + if (metadata?.[0]?.skip) return undefined; if (metadata) return metadata; // Fall back to default metadata return this.options.defaultMetadata; diff --git a/packages/authentication/src/providers/auth-strategy.provider.ts b/packages/authentication/src/providers/auth-strategy.provider.ts index 3d23742d7fa8..143c6baab058 100644 --- a/packages/authentication/src/providers/auth-strategy.provider.ts +++ b/packages/authentication/src/providers/auth-strategy.provider.ts @@ -37,16 +37,18 @@ export class AuthenticationStrategyProvider @extensions() protected authenticationStrategies: Getter, @inject(AuthenticationBindings.METADATA) - protected metadata?: AuthenticationMetadata, + protected metadata?: AuthenticationMetadata[], ) {} async value(): Promise { - if (!this.metadata) { + if (!this.metadata?.length) { return undefined; } - return this.findAuthenticationStrategies(this.metadata.strategy); + return this.findAuthenticationStrategies(this.metadata); } - private async findAuthenticationStrategies(names: string | string[]) { + private async findAuthenticationStrategies( + metadata: AuthenticationMetadata[], + ): Promise { const strategies: AuthenticationStrategy[] = []; const existingStrategies = await this.authenticationStrategies(); @@ -63,13 +65,8 @@ export class AuthenticationStrategyProvider return strategy; }; - if (Array.isArray(names)) { - for (const name of names) { - const strategy = findStrategy(name); - strategies.push(strategy); - } - } else { - const strategy = findStrategy(names); + for (const data of metadata) { + const strategy = findStrategy(data.strategy); strategies.push(strategy); } diff --git a/packages/authentication/src/types.ts b/packages/authentication/src/types.ts index 46864797fc3e..6d2ddf62fa2a 100644 --- a/packages/authentication/src/types.ts +++ b/packages/authentication/src/types.ts @@ -11,7 +11,7 @@ import { Context, extensionFor, } from '@loopback/core'; -import {Request, RedirectRoute} from '@loopback/rest'; +import {RedirectRoute, Request} from '@loopback/rest'; import {UserProfile} from '@loopback/security'; import {AuthenticationBindings} from './keys'; @@ -22,7 +22,7 @@ export interface AuthenticationMetadata { /** * Name of the authentication strategy */ - strategy: string | string[]; + strategy: string; /** * Options for the authentication strategy */ @@ -59,7 +59,7 @@ export interface AuthenticationOptions { * `@authenticate`. If not set, no default authentication will be enforced for * those methods without authentication metadata. */ - defaultMetadata?: AuthenticationMetadata; + defaultMetadata?: AuthenticationMetadata[]; } /** @@ -133,3 +133,16 @@ export const asAuthStrategy: BindingTemplate = binding => { AuthenticationBindings.AUTHENTICATION_STRATEGY_EXTENSION_POINT_NAME, }); }; + +/** + * Get the authentication metadata object for the specified strategy. + * + * @param metadata - Array of authentication metadata objects + * @param strategyName - Name of the authentication strategy + */ +export function getAuthenticationMetadataForStrategy( + metadata: AuthenticationMetadata[], + strategyName: string, +): AuthenticationMetadata | undefined { + return metadata.find(data => data.strategy === strategyName); +}