Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 7 additions & 8 deletions docs/site/Sequence.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ Here is an example where the application logs out a message before and after
a request is handled:

```ts
import {DefaultSequence, ParsedRequest, ServerResponse} from '@loopback/rest';
import {DefaultSequence, Request, Response} from '@loopback/rest';

class MySequence extends DefaultSequence {
log(msg: string) {
Expand Down Expand Up @@ -137,7 +137,7 @@ function upon injection.
**custom-send.provider.ts**

```ts
import {Send, ServerResponse} from '@loopback/rest';
import {Send, Response} from '@loopback/rest';
import {Provider, BoundValue, inject} from '@loopback/context';
import {
writeResultToResponse,
Expand All @@ -157,19 +157,18 @@ export class CustomSendProvider implements Provider<Send> {

value() {
// Use the lambda syntax to preserve the "this" scope for future calls!
return (response: ServerResponse, result: OperationRetval) => {
return (response: Response, result: OperationRetval) => {
this.action(response, result);
};
}
/**
* Use the mimeType given in the request's Accept header to convert
* the response object!
* @param ServerResponse response The response object used to reply to the
* client.
* @param OperationRetVal result The result of the operation carried out by
* the controller's handling function.
* @param response The response object used to reply to the client.
* @param result The result of the operation carried out by the controller's
* handling function.
*/
action(response: ServerResponse, result: OperationRetval) {
action(response: Response, result: OperationRetval) {
if (result) {
// Currently, the headers interface doesn't allow arbitrary string keys!
const headers = (this.request.headers as any) || {};
Expand Down
6 changes: 3 additions & 3 deletions examples/log-extension/src/providers/log-action.provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import {inject, Provider, Constructor, Getter} from '@loopback/context';
import {CoreBindings} from '@loopback/core';
import {OperationArgs, ParsedRequest} from '@loopback/rest';
import {OperationArgs, Request} from '@loopback/rest';
import {getLogMetadata} from '../decorators';
import {EXAMPLE_LOG_BINDINGS, LOG_LEVEL} from '../keys';
import {
Expand Down Expand Up @@ -35,7 +35,7 @@ export class LogActionProvider implements Provider<LogFn> {

value(): LogFn {
const fn = <LogFn>((
req: ParsedRequest,
req: Request,
args: OperationArgs,
// tslint:disable-next-line:no-any
result: any,
Expand All @@ -52,7 +52,7 @@ export class LogActionProvider implements Provider<LogFn> {
}

private async action(
req: ParsedRequest,
req: Request,
args: OperationArgs,
// tslint:disable-next-line:no-any
result: any,
Expand Down
4 changes: 2 additions & 2 deletions examples/log-extension/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@

// Types and interfaces exposed by the extension go here

import {ParsedRequest, OperationArgs} from '@loopback/rest';
import {Request, OperationArgs} from '@loopback/rest';

/**
* A function to perform REST req/res logging action
*/
export interface LogFn {
(
req: ParsedRequest,
req: Request,
args: OperationArgs,
// tslint:disable-next-line:no-any
result: any,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// License text available at https://opensource.org/licenses/MIT

import {sinon} from '@loopback/testlab';
import {ParsedRequest} from '@loopback/rest';
import {Request} from '@loopback/rest';
import {
LogActionProvider,
LogFn,
Expand All @@ -21,7 +21,7 @@ import {logToMemory} from '../../in-memory-logger';
describe('LogActionProvider with in-memory logger', () => {
let spy: sinon.SinonSpy;
let logger: LogFn;
const req = <ParsedRequest>{url: '/test'};
const req = <Request>{url: '/test'};

beforeEach(() => {
spy = createLogSpy();
Expand Down Expand Up @@ -60,7 +60,7 @@ describe('LogActionProvider with in-memory logger', () => {
describe('LogActionProvider with default logger', () => {
let stub: sinon.SinonSpy;
let logger: LogFn;
const req = <ParsedRequest>{url: '/test'};
const req = <Request>{url: '/test'};

beforeEach(() => {
stub = createConsoleStub();
Expand Down
3 changes: 1 addition & 2 deletions packages/authentication/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,10 @@ import {
InvokeMethod,
Send,
Reject,
ParsedRequest,
RequestContext,
} from '@loopback/rest';
import {inject} from '@loopback/context';
import {AuthenticationBindings, AuthenticateFn} from '@loopback/authentication';
import {ServerResponse} from 'http';

const SequenceActions = RestBindings.SequenceActions;

Expand Down
4 changes: 1 addition & 3 deletions packages/authentication/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

export * from './types';
export * from './authentication.component';
export * from './decorators';
export * from './keys';
export * from './strategy-adapter';

// internals for tests
export * from './providers';
16 changes: 8 additions & 8 deletions packages/authentication/src/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// License text available at https://opensource.org/licenses/MIT

import {Strategy} from 'passport';
import {AuthenticateFn, UserProfile} from './providers/authentication.provider';
import {AuthenticateFn, UserProfile} from './types';
import {AuthenticationMetadata} from './decorators/authenticate.decorator';
import {BindingKey, MetadataAccessor} from '@loopback/context';

Expand Down Expand Up @@ -37,20 +37,20 @@ export namespace AuthenticationBindings {
* // ... other sequence action injections
* ) {}
*
* async handle(req: ParsedRequest, res: ServerResponse) {
* async handle(context: RequestContext) {
* try {
* const route = this.findRoute(req);
* const {request, response} = context;
* const route = this.findRoute(request);
*
* // Authenticate
* await this.authenticateRequest(req);
* await this.authenticateRequest(request);
*
* // Authentication successful, proceed to invoke controller
* const args = await this.parseParams(req, route);
* const args = await this.parseParams(request, route);
* const result = await this.invoke(route, args);
* this.send(res, result);
* this.send(response, result);
* } catch (err) {
* this.reject(res, req, err);
* return;
* this.reject(context, err);
* }
* }
* }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,12 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {ParsedRequest} from '@loopback/rest';
import {inject} from '@loopback/core';
import {Provider, Getter, Setter} from '@loopback/context';
import {Getter, Provider, Setter, inject} from '@loopback/context';
import {Request} from '@loopback/rest';
import {Strategy} from 'passport';
import {StrategyAdapter} from '../strategy-adapter';
import {AuthenticationBindings} from '../keys';

/**
* Passport monkey-patches Node.js' IncomingMessage prototype
* and adds extra methods like "login" and "isAuthenticated"
*/
export type PassportRequest = ParsedRequest & Express.Request;

/**
* interface definition of a function which accepts a request
* and returns an authenticated user
*/
export interface AuthenticateFn {
(request: ParsedRequest): Promise<UserProfile | undefined>;
}

/**
* interface definition of a user profile
* http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims
*/
export interface UserProfile {
id: string;
name?: string;
email?: string;
}
import {StrategyAdapter} from '../strategy-adapter';
import {AuthenticateFn, UserProfile} from '../types';

/**
* @description Provider of a function which authenticates
Expand Down Expand Up @@ -63,9 +39,9 @@ export class AuthenticateActionProvider implements Provider<AuthenticateFn> {

/**
* The implementation of authenticate() sequence action.
* @param request Parsed Request
* @param request The incoming request provided by the REST layer
*/
async action(request: ParsedRequest): Promise<UserProfile | undefined> {
async action(request: Request): Promise<UserProfile | undefined> {
const strategy = await this.getStrategy();
if (!strategy) {
// The invoked operation does not require authentication.
Expand Down
67 changes: 12 additions & 55 deletions packages/authentication/src/strategy-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,59 +3,11 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {HttpErrors, ParsedRequest} from '@loopback/rest';
import {HttpErrors, Request} from '@loopback/rest';
import {Strategy} from 'passport';
import {UserProfile} from './providers/authentication.provider';
import {UserProfile} from './types';

const PassportRequestExtras: Express.Request = require('passport/lib/http/request');

/**
* Shimmed Request to satisfy express requirements of passport strategies.
*/
export class ShimRequest implements Express.Request {
headers: Object;
query: Object;
url: string;
path: string;
method: string;

constructor(request: ParsedRequest) {
this.headers = request.headers;
this.query = request.query;
this.url = request.url;
this.path = request.path;
this.method = request.method;
}

// tslint:disable:no-any
login(user: any, done: (err: any) => void): void;
login(user: any, options: any, done: (err: any) => void): void;
login(user: any, options: any, done?: any) {
PassportRequestExtras.login.apply(this, arguments);
}

logIn(user: any, done: (err: any) => void): void;
logIn(user: any, options: any, done: (err: any) => void): void;
logIn(user: any, options: any, done?: any) {
PassportRequestExtras.logIn.apply(this, arguments);
}

logout(): void {
PassportRequestExtras.logout.apply(this, arguments);
}

logOut(): void {
PassportRequestExtras.logOut.apply(this, arguments);
}

isAuthenticated(): boolean {
return PassportRequestExtras.isAuthenticated.apply(this, arguments);
}
isUnauthenticated(): boolean {
return PassportRequestExtras.isUnauthenticated.apply(this, arguments);
}
// tslint:enable:no-any
}
const passportRequestMixin = require('passport/lib/http/request');

/**
* Adapter class to invoke passport-strategy
Expand All @@ -77,11 +29,16 @@ export class StrategyAdapter {
* 1. Create an instance of the strategy
* 2. add success and failure state handlers
* 3. authenticate using the strategy
* @param req {http.ServerRequest} The incoming request.
* @param request The incoming request.
*/
authenticate(req: ParsedRequest) {
const shimReq = new ShimRequest(req);
authenticate(request: Request): Promise<UserProfile> {
return new Promise<UserProfile>((resolve, reject) => {
// mix-in passport additions like req.logIn and req.logOut
for (const key in passportRequestMixin) {
// tslint:disable-next-line:no-any
(request as any)[key] = passportRequestMixin[key];
}

// create a prototype chain of an instance of a passport strategy
const strategy = Object.create(this.strategy);

Expand All @@ -101,7 +58,7 @@ export class StrategyAdapter {
};

// authenticate
strategy.authenticate(shimReq);
strategy.authenticate(request);
});
}
}
24 changes: 24 additions & 0 deletions packages/authentication/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright IBM Corp. 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 {Request} from '@loopback/rest';

/**
* interface definition of a function which accepts a request
* and returns an authenticated user
*/
export interface AuthenticateFn {
(request: Request): Promise<UserProfile | undefined>;
}

/**
* interface definition of a user profile
* http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims
*/
export interface UserProfile {
id: string;
name?: string;
email?: string;
}
21 changes: 9 additions & 12 deletions packages/authentication/test/unit/fixtures/mock-strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// License text available at https://opensource.org/licenses/MIT

import {Strategy, AuthenticateOptions} from 'passport';
import {PassportRequest} from '../../..';
import {Request} from 'express';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be imported from @loopback/rest?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is my reasoning: MockStrategy is extending from Passport Strategy, which uses Request from express.

If we ever change @loopback/rest to export Request as a different type, then MockStrategy still needs to keep using Request from express, because that's what the Strategy interface requires.


/**
* Test fixture for a mock asynchronous passport-strategy
Expand All @@ -21,7 +21,7 @@ export class MockStrategy extends Strategy {
* authenticate() function similar to passport-strategy packages
* @param req
*/
async authenticate(req: Express.Request, options?: AuthenticateOptions) {
async authenticate(req: Request, options?: AuthenticateOptions) {
await this.verify(req);
}
/**
Expand All @@ -33,21 +33,18 @@ export class MockStrategy extends Strategy {
* pass req.query.testState = 'fail' to mock failed authorization
* pass req.query.testState = 'error' to mock unexpected error
*/
async verify(request: Express.Request) {
// A workaround for buggy typings provided by @types/passport
const req = request as PassportRequest;

async verify(request: Request) {
if (
req.headers &&
req.headers.testState &&
req.headers.testState === 'fail'
request.headers &&
request.headers.testState &&
request.headers.testState === 'fail'
) {
this.returnUnauthorized('authorization failed');
return;
} else if (
req.headers &&
req.headers.testState &&
req.headers.testState === 'error'
request.headers &&
request.headers.testState &&
request.headers.testState === 'error'
) {
this.returnError('unexpected error');
return;
Expand Down
Loading