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 ed1a3fa
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 6 deletions.
8 changes: 8 additions & 0 deletions packages/authentication/src/constants.ts
@@ -0,0 +1,8 @@
// Copyright IBM Corp. 2017. All Rights Reserved.
// Node module: @loopback/authentication
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

export class Keys {
static readonly AUTHENTICATION_KEY: string = 'authenticate';
}
15 changes: 9 additions & 6 deletions packages/authentication/src/decorator.ts
Expand Up @@ -4,11 +4,12 @@
// License text available at https://opensource.org/licenses/MIT

import { Reflector } from '@loopback/context';
import { Keys } from './constants';

/* 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_KEY, 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(controllerObj: Object, methodName: string): Metadata {
const metadataObj: AuthenticationMetadata = Reflector.getMetadata(Keys.AUTHENTICATION_KEY, controllerObj, methodName);
if (metadataObj)
return metadataObj.getMetadata();
throw new Error('No metadata defined');
}
2 changes: 2 additions & 0 deletions packages/authentication/src/index.ts
Expand Up @@ -5,3 +5,5 @@

export * from './decorator';
export * from './strategy-adapter';
export * from './metadata-provider';
export * from './constants';
26 changes: 26 additions & 0 deletions packages/authentication/src/metadata-provider.ts
@@ -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 {RequestContextkeys} from '@loopback/core';
import {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 implements Provider<Metadata> {
constructor(
@inject(RequestContextkeys.CONTROLLER_CLASS) private readonly controllerClass: Object,
@inject(RequestContextkeys.CONTROLLER_METHOD_NAME) private readonly methodName: string,
) {}

/**
* @returns IMetadata
*/
value(): Metadata {
return getAuthenticateMetadata(this.controllerClass, this.methodName);
}
}
73 changes: 73 additions & 0 deletions packages/authentication/test/unit/metadata-provider.ts
@@ -0,0 +1,73 @@
// 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, RequestContextkeys} 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() {}
}

const testController: TestController = new TestController();
const controllerNoMetadata: ControllerWithNoMetadata = new ControllerWithNoMetadata();

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(RequestContextkeys.CONTROLLER_CLASS).to(testController);
context.bind(RequestContextkeys.CONTROLLER_METHOD_NAME).to('whoAmI');
context.bind(RequestContextkeys.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(RequestContextkeys.CONTROLLER_CLASS).to(testController);
context.bind(RequestContextkeys.CONTROLLER_METHOD_NAME).to('whoAmI');
context.bind(RequestContextkeys.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(RequestContextkeys.CONTROLLER_CLASS).to(controllerNoMetadata);
context.bind(RequestContextkeys.CONTROLLER_METHOD_NAME).to('whoAmI');
context.bind(RequestContextkeys.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');
}
});
10 changes: 10 additions & 0 deletions packages/core/src/constants.ts
@@ -0,0 +1,10 @@
// Copyright IBM Corp. 2017. All Rights Reserved.
// Node module: @loopback/authentication
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

export class RequestContextkeys {
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';
}
1 change: 1 addition & 0 deletions packages/core/src/index.ts
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 {RequestContextkeys} from './constants';

0 comments on commit ed1a3fa

Please sign in to comment.