Skip to content

Commit

Permalink
Authentication metadata provider
Browse files Browse the repository at this point in the history
Implement provider to return controller method metadata
  • Loading branch information
deepakrkris committed Jun 11, 2017
1 parent 25b95f9 commit 4662193
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 13 deletions.
17 changes: 10 additions & 7 deletions packages/authentication/src/decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import { Reflector } from '@loopback/context';
import {Reflector, Constructor} from '@loopback/context';
import {Keys} from '@loopback/core';

/* interface to define authentication metadata structure expected in
* json objects
*/
export interface IMetadata {
export interface Metadata {
// name of the passport strategy to use for authentication
strategy: string;
// options to configure the passport strategy
Expand All @@ -25,7 +26,7 @@ export class AuthenticationMetadata {
this.strategyName = strategy;
this.options = optionValues || {};
}
getMetadata(): IMetadata {
getMetadata(): Metadata {
return {strategy: this.strategyName, options: this.options};
}
}
Expand All @@ -38,7 +39,7 @@ export class AuthenticationMetadata {
export function authenticate(strategyName: string, options? : Object) {
return function(controllerClass: Object, methodName: string) {
const metadataObj: AuthenticationMetadata = new AuthenticationMetadata(strategyName, options || {});
Reflector.defineMetadata('loopback:authenticate', metadataObj, controllerClass, methodName);
Reflector.defineMetadata(Keys.Authentication.DECORATOR, metadataObj, controllerClass, methodName);
};
}

Expand All @@ -47,7 +48,9 @@ export function authenticate(strategyName: string, options? : Object) {
* @param controllerObj
* @param methodName
*/
export function getAuthenticateMetadata(controllerObj: Object, methodName: string): IMetadata {
const metadataObj: AuthenticationMetadata = Reflector.getMetadata('loopback:authenticate', controllerObj, methodName);
return metadataObj.getMetadata();
export function getAuthenticateMetadata<T>(controllerClass: Constructor<T>, methodName: string): Metadata {
const metadataObj: AuthenticationMetadata = Reflector.getMetadata(Keys.Authentication.DECORATOR, controllerClass.prototype, methodName);
if (metadataObj)
return metadataObj.getMetadata();
throw new Error('No metadata defined');
}
1 change: 1 addition & 0 deletions packages/authentication/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@

export * from './decorator';
export * from './strategy-adapter';
export * from './metadata-provider';
26 changes: 26 additions & 0 deletions packages/authentication/src/metadata-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright IBM Corp. 2017. All Rights Reserved.
// Node module: loopback
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {Keys} from '@loopback/core';
import {Constructor, Provider, inject} from '@loopback/context';
import {Metadata, getAuthenticateMetadata} from './decorator';

/**
* @description Provides authentication metadata of a controller method
* @example `context.bind('authentication.meta').toProvider(AuthMetadataProvider)`
*/
export class AuthMetadataProvider<T> implements Provider<Metadata> {
constructor(
@inject(Keys.Context.CONTROLLER_CLASS) private readonly controllerClass: Constructor<T>,
@inject(Keys.Context.CONTROLLER_METHOD_NAME) private readonly methodName: string,
) {}

/**
* @returns IMetadata
*/
value(): Metadata {
return getAuthenticateMetadata(this.controllerClass, this.methodName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ describe('Authentication', () => {
async whoAmI() {}
}

const test: TestClass = new TestClass();

const metaData = getAuthenticateMetadata(test, 'whoAmI');
const metaData = getAuthenticateMetadata(TestClass, 'whoAmI');
const expectedMetaDataObject = {strategy: 'my-strategy', options: {option1: 'value1', option2: 'value2'}};
expect(metaData).to.deepEqual(expectedMetaDataObject);
});
Expand All @@ -43,9 +41,7 @@ describe('Authentication', () => {
async whoAmI() {}
}

const test: TestClass = new TestClass();

const metaData = getAuthenticateMetadata(test, 'whoAmI');
const metaData = getAuthenticateMetadata(TestClass, 'whoAmI');
const expectedMetaDataObject = {strategy: 'my-strategy', options: {}};
expect(metaData).to.deepEqual(expectedMetaDataObject);
});
Expand Down
70 changes: 70 additions & 0 deletions packages/authentication/test/unit/metadata-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright IBM Corp. 2017. All Rights Reserved.
// Node module: loopback
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {expect} from '@loopback/testlab';
import {Context, Provider} from '@loopback/context';
import {ParsedRequest, Keys} from '@loopback/core';
import {AuthMetadataProvider, Metadata, authenticate} from '../..';

describe('AuthMetadataProvider', () => {
let provider: Provider<Metadata>;

class TestController {
@authenticate('my-strategy', {option1: 'value1', option2: 'value2'})
async whoAmI() {}
}

class ControllerWithNoMetadata {
async whoAmI() {}
}

beforeEach(givenAuthMetadataProvider);

describe('value()', () => {
it('returns the authentication metadata of a controller method', async () => {
const authMetadata: Metadata = await Promise.resolve(provider.value());
expect(authMetadata).to.be.eql({strategy: 'my-strategy', options: {option1: 'value1', option2: 'value2'}});
});

describe('context.get(provider_key)', () => {
it('returns the authentication metadata of a controller method', async () => {
const context: Context = new Context();
context.bind(Keys.Context.CONTROLLER_CLASS).to(TestController);
context.bind(Keys.Context.CONTROLLER_METHOD_NAME).to('whoAmI');
context.bind(Keys.Context.CONTROLLER_METHOD_META).toProvider(AuthMetadataProvider);
const authMetadata = await context.get('controller.method.meta');
expect(authMetadata).to.be.eql({strategy: 'my-strategy', options: {option1: 'value1', option2: 'value2'}});
});
});

describe('context.get(provider_key)', () => {
it('returns the authentication metadata of a controller method', async () => {
const context: Context = new Context();
context.bind(Keys.Context.CONTROLLER_CLASS).to(TestController);
context.bind(Keys.Context.CONTROLLER_METHOD_NAME).to('whoAmI');
context.bind(Keys.Context.CONTROLLER_METHOD_META).toProvider(AuthMetadataProvider);
const authMetadata = await context.get('controller.method.meta');
expect(authMetadata).to.be.eql({strategy: 'my-strategy', options: {option1: 'value1', option2: 'value2'}});
});

it('throws error if no authentication metadata is defined', async () => {
const context: Context = new Context();
context.bind(Keys.Context.CONTROLLER_CLASS).to(ControllerWithNoMetadata);
context.bind(Keys.Context.CONTROLLER_METHOD_NAME).to('whoAmI');
context.bind(Keys.Context.CONTROLLER_METHOD_META).toProvider(AuthMetadataProvider);
try {
await context.get('controller.method.meta');
expect.fail('0', '1', 'Metadata error should have been thrown', '');
} catch (err) {
expect(err).to.have.property('message', 'No metadata defined');
}
});
});
});

function givenAuthMetadataProvider() {
provider = new AuthMetadataProvider(TestController, 'whoAmI');
}
});
16 changes: 16 additions & 0 deletions packages/core/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright IBM Corp. 2017. All Rights Reserved.
// Node module: @loopback/core
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

export namespace Keys {
export class Context {
static readonly CONTROLLER_CLASS: string = 'controller.class';
static readonly CONTROLLER_METHOD_NAME: string = 'controller.method.name';
static readonly CONTROLLER_METHOD_META: string = 'controller.method.meta';
}

export class Authentication {
static readonly DECORATOR: string = 'authenticate';
}
}
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ export {parseRequestUrl} from './router/routing-table';
export {RoutingTable, ResolvedRoute} from './router/routing-table';
export {HttpHandler} from './http-handler';
export {writeResultToResponse} from './writer';
export * from './constants';

0 comments on commit 4662193

Please sign in to comment.