diff --git a/.gitignore b/.gitignore index f62f570e7480..c3f28b5fc666 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ api-docs **/*.tgz packages/*/dist examples/*/dist +experimental/*/dist benchmark/dist **/package .sandbox diff --git a/.prettierignore b/.prettierignore index e8c9b7b64619..a0e489e1864a 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,6 +4,7 @@ packages/cli/generators/*/templates **/.sandbox packages/*/dist examples/*/dist +experimental/*/dist benchmark/dist sandbox/**/* *.json diff --git a/bin/mark-non-lab-packages-private.js b/bin/mark-non-lab-packages-private.js new file mode 100644 index 000000000000..81cd73995c70 --- /dev/null +++ b/bin/mark-non-lab-packages-private.js @@ -0,0 +1,42 @@ +#!/usr/bin/env node +// Copyright IBM Corp. 2017,2018. All Rights Reserved. +// Node module: loopback-next +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +/** + * This is an internal script to set all non-labs packages to be private + */ +'use strict'; + +const path = require('path'); +const fs = require('fs'); + +const Project = require('@lerna/project'); + +async function markNonLabsPackagesPrivate() { + const project = new Project(process.cwd()); + const packages = await project.getPackages(); + + // Set `"private": "true"` in individual packages + for (const pkg of packages) { + const dir = path.relative(pkg.rootPath, pkg.location); + if (dir.startsWith('labs')) continue; + const data = readJsonFile(pkg.manifestLocation); + let modified = !data.private; + if (!modified) continue; + data.private = true; + writeJsonFile(pkg.manifestLocation, data, dir); + } +} + +if (require.main === module) markNonLabsPackagesPrivate(); + +function readJsonFile(filePath) { + return JSON.parse(fs.readFileSync(filePath, 'utf-8')); +} + +function writeJsonFile(filePath, data, dir) { + fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf-8'); + console.log('> %s has been marked as private.', dir || filePath); +} diff --git a/docs/package.json b/docs/package.json index 5b7b83c0884b..95fb8714a932 100644 --- a/docs/package.json +++ b/docs/package.json @@ -33,5 +33,6 @@ "license": "MIT", "dependencies": { "fs-extra": "^8.0.0" - } + }, + "private": true } diff --git a/examples/express-composition/package.json b/examples/express-composition/package.json index 007d452ed0da..c23b3224c8d1 100644 --- a/examples/express-composition/package.json +++ b/examples/express-composition/package.json @@ -62,5 +62,6 @@ "@types/node": "^10.11.2", "tslint": "^5.16.0", "typescript": "^3.4.5" - } + }, + "private": true } diff --git a/examples/greeter-extension/package.json b/examples/greeter-extension/package.json index 3070d7def3c5..5b2afd50ff4a 100644 --- a/examples/greeter-extension/package.json +++ b/examples/greeter-extension/package.json @@ -58,5 +58,6 @@ "@loopback/rest": "^1.12.0", "chalk": "^2.4.2", "debug": "^4.0.1" - } + }, + "private": true } diff --git a/examples/hello-world/package.json b/examples/hello-world/package.json index 5507a8398857..beeb5972d36d 100644 --- a/examples/hello-world/package.json +++ b/examples/hello-world/package.json @@ -53,5 +53,6 @@ "LoopBack", "example", "tutorial" - ] + ], + "private": true } diff --git a/examples/log-extension/package.json b/examples/log-extension/package.json index eba6e3345618..33f797a23172 100644 --- a/examples/log-extension/package.json +++ b/examples/log-extension/package.json @@ -57,5 +57,6 @@ "@loopback/rest": "^1.12.0", "chalk": "^2.3.2", "debug": "^4.0.1" - } + }, + "private": true } diff --git a/examples/rpc-server/package.json b/examples/rpc-server/package.json index 9a8c37d77ec3..a15b4cdff988 100644 --- a/examples/rpc-server/package.json +++ b/examples/rpc-server/package.json @@ -51,5 +51,6 @@ "@types/node": "^10.11.2", "tslint": "^5.16.0", "typescript": "^3.4.5" - } + }, + "private": true } diff --git a/examples/soap-calculator/package.json b/examples/soap-calculator/package.json index 4a12d6ce4de1..7692d2d9a321 100644 --- a/examples/soap-calculator/package.json +++ b/examples/soap-calculator/package.json @@ -61,5 +61,6 @@ "source-map-support": "^0.5.5", "tslint": "^5.16.0", "typescript": "^3.4.5" - } + }, + "private": true } diff --git a/examples/todo-list/package.json b/examples/todo-list/package.json index 1874d7f88bad..646a4b13fbd9 100644 --- a/examples/todo-list/package.json +++ b/examples/todo-list/package.json @@ -68,5 +68,6 @@ "models", "todo", "HasMany" - ] + ], + "private": true } diff --git a/examples/todo/package.json b/examples/todo/package.json index db9faa69c12a..c4c052cec972 100644 --- a/examples/todo/package.json +++ b/examples/todo/package.json @@ -66,5 +66,6 @@ "CRUD", "models", "todo" - ] + ], + "private": true } diff --git a/experimental/passport-adapter/.npmrc b/experimental/passport-adapter/.npmrc new file mode 100644 index 000000000000..cafe685a112d --- /dev/null +++ b/experimental/passport-adapter/.npmrc @@ -0,0 +1 @@ +package-lock=true diff --git a/experimental/passport-adapter/LICENSE b/experimental/passport-adapter/LICENSE new file mode 100644 index 000000000000..6d928a4b885a --- /dev/null +++ b/experimental/passport-adapter/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) IBM Corp. 2017,2019. All Rights Reserved. +Node module: @loopback/authentication +This project is licensed under the MIT License, full text below. + +-------- + +MIT license + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/experimental/passport-adapter/README.md b/experimental/passport-adapter/README.md new file mode 100644 index 000000000000..567a6a796237 --- /dev/null +++ b/experimental/passport-adapter/README.md @@ -0,0 +1,111 @@ +## Passport Adapter + +*Important: We suggest users understand LoopBack's authentication system[Link TBD](some loopback.io link) before using this module* + +This is an adapter module created for plugging in [`passport`](https://www.npmjs.com/package/passport) base strategies to the authentication system in `@loopback/authentication@2.x`. + +## Installation + +```sh +npm i @loopback/passport-adapter --save +``` + +## Background + +`@loopback/authentication@2.x` allows users to register authentication strategies that implement the interface [`AuthenticationStrategy`](https://apidocs.strongloop.com/@loopback%2fdocs/authentication.html#AuthenticationStrategy) + +Since `AuthenticationStrategy` describes a strategy with different contracts than the passport [`Strategy`](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/passport/index.d.ts#L79), and we'd like to support the existing 500+ community passport strategies, an **adapter class** is created in this package to convert a passport strategy to the one that LoopBack 4 authentication system wants. + +## Usage + +1. Create a provider for the strategy + +Take the basic strategy exported from [`passport-http`](https://github.com/jaredhanson/passport-http) as an example, create a provider that returns a converted basic strategy: + +*Note: If you are not familiar with LoopBack provider, check the documentations in [Extending LoopBack 4](https://loopback.io/doc/en/lb4/Extending-LoopBack-4.html)* + +```ts + class PassportBasicAuthProvider implements Provider { + value(): AuthenticationStrategy { + // The code that returns the converted strategy + } + } +``` + +The Provider should have two functions: + +- A function that takes in the verify callback function and returns a configured basic strategy. To know more about the configuration, please check [the configuration guide in module `passport-http`](https://github.com/jaredhanson/passport-http#usage-of-http-basic). + +- A function that applies the `StrategyAdapter` to the configured basic strategy instance. Then in the `value()` function, you return the converted strategy. + +So a full implementation of the provider is: + +```ts +import {BasicStrategy, BasicVerifyFunction} from 'passport-http'; +import {StrategyAdapter} from `@loopback/passport-adapter`; +import {AuthenticationStrategy} from '@loopback/authentication'; + +class PassportBasicAuthProvider implements Provider { + value(): AuthenticationStrategy { + const basicStrategy = this.configuredBasicStrategy(verify); + return this.convertToAuthStrategy(basicStrategy); + } + + // Takes in the verify callback function and returns a configured basic strategy. + configuredBasicStrategy(verifyFn: BasicVerifyFunction): BasicStrategy { + return new BasicStrategy(verifyFn); + } + + // Applies the `StrategyAdapter` to the configured basic strategy instance. + // You'd better define your strategy name as a constant, like + // `const AUTH_STRATEGY_NAME = 'basic'` + // So that you will decorate the APIs later with the same name + convertToAuthStrategy(basic: BasicStrategy): AuthenticationStrategy { + return new StrategyAdapter(basic, AUTH_STRATEGY_NAME); + } +} +``` + +2. Register the strategy provider + +Register the strategy provider in your LoopBack application so that the authentication system can look for your strategy by name and invoke it: + +```ts +// In the main file + +import {addExtension} from '@loopback/core'; +import {MyApplication} from ''; +import {AuthenticationBindings} from '@loopback/authentication'; + +const app = new MyApplication(); + +addExtension( + app, + AuthenticationBindings.AUTHENTICATION_STRATEGY_EXTENSION_POINT_NAME, + PassportBasicAuthProvider, + { + namespace: + AuthenticationBindings.AUTHENTICATION_STRATEGY_EXTENSION_POINT_NAME, + }, +); +``` + +3. Decorate your endpoint + +To authenticate your request with the basic strategy, decorate your controller function like: + +```ts +class MyController { + constructor( + @inject(AuthenticationBindings.CURRENT_USER) private user: UserProfile, + ) {} + + // Define your strategy name as a constant so that + // it is consistent with the name you provide in the adapter + @authenticate(AUTH_STRATEGY_NAME) + async whoAmI(): Promise { + return this.user.id; + } + } +``` + diff --git a/experimental/passport-adapter/dist/__tests__/acceptance/authentication-with-passport-strategy.acceptance.d.ts b/experimental/passport-adapter/dist/__tests__/acceptance/authentication-with-passport-strategy.acceptance.d.ts new file mode 100644 index 000000000000..cb0ff5c3b541 --- /dev/null +++ b/experimental/passport-adapter/dist/__tests__/acceptance/authentication-with-passport-strategy.acceptance.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/experimental/passport-adapter/dist/__tests__/acceptance/authentication-with-passport-strategy.acceptance.js b/experimental/passport-adapter/dist/__tests__/acceptance/authentication-with-passport-strategy.acceptance.js new file mode 100644 index 000000000000..931a503b86ea --- /dev/null +++ b/experimental/passport-adapter/dist/__tests__/acceptance/authentication-with-passport-strategy.acceptance.js @@ -0,0 +1,207 @@ +"use strict"; +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/authentication +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const context_1 = require("@loopback/context"); +const core_1 = require("@loopback/core"); +const openapi_spec_builder_1 = require("@loopback/openapi-spec-builder"); +const openapi_v3_1 = require("@loopback/openapi-v3"); +const rest_1 = require("@loopback/rest"); +const testlab_1 = require("@loopback/testlab"); +const passport_http_1 = require("passport-http"); +const authentication_1 = require("@loopback/authentication"); +const __1 = require("../../"); +const SequenceActions = rest_1.RestBindings.SequenceActions; +const AUTH_STRATEGY_NAME = 'basic'; +describe('Basic Authentication', () => { + let app; + let server; + let users; + beforeEach(givenAServer); + beforeEach(givenUserRepository); + beforeEach(givenControllerInApp); + beforeEach(givenAuthenticatedSequence); + it('authenticates successfully for correct credentials', async () => { + const client = whenIMakeRequestTo(server); + const credential = users.list.joe.profile.id + ':' + users.list.joe.password; + const hash = Buffer.from(credential).toString('base64'); + await client + .get('/whoAmI') + .set('Authorization', 'Basic ' + hash) + .expect(users.list.joe.profile.id); + }); + it('returns error for invalid credentials', async () => { + const client = whenIMakeRequestTo(server); + const credential = users.list.Simpson.profile.id + ':' + 'invalid'; + const hash = Buffer.from(credential).toString('base64'); + await client + .get('/whoAmI') + .set('Authorization', 'Basic ' + hash) + .expect(401); + }); + it('allows anonymous requests to methods with no decorator', async () => { + class InfoController { + status() { + return { running: true }; + } + } + __decorate([ + openapi_v3_1.get('/status'), + __metadata("design:type", Function), + __metadata("design:paramtypes", []), + __metadata("design:returntype", void 0) + ], InfoController.prototype, "status", null); + app.controller(InfoController); + await whenIMakeRequestTo(server) + .get('/status') + .expect(200, { running: true }); + }); + function givenUserRepository() { + users = new UserRepository({ + joe: { profile: { id: 'joe' }, password: '12345' }, + Simpson: { profile: { id: 'sim123' }, password: 'alpha' }, + Flinstone: { profile: { id: 'Flint' }, password: 'beta' }, + George: { profile: { id: 'Curious' }, password: 'gamma' }, + }); + } + // Since it has to be user's job to provide the `verify` function and + // instantiate the passport strategy, we cannot add the imported `BasicStrategy` + // class as extension directly, we need to wrap it as a strategy provider, + // then add the provider class as the extension. + // See Line 89 in the function `givenAServer` + class PassportBasicAuthProvider { + value() { + const basicStrategy = this.configuredBasicStrategy(verify); + return this.convertToAuthStrategy(basicStrategy); + } + configuredBasicStrategy(verifyFn) { + return new passport_http_1.BasicStrategy(verifyFn); + } + convertToAuthStrategy(basic) { + return new __1.StrategyAdapter(basic, AUTH_STRATEGY_NAME); + } + } + function verify(username, password, cb) { + process.nextTick(() => { + users.find(username, password, cb); + }); + } + async function givenAServer() { + app = new core_1.Application(); + app.component(authentication_1.AuthenticationComponent); + app.component(rest_1.RestComponent); + core_1.addExtension(app, authentication_1.AuthenticationBindings.AUTHENTICATION_STRATEGY_EXTENSION_POINT_NAME, PassportBasicAuthProvider, { + namespace: authentication_1.AuthenticationBindings.AUTHENTICATION_STRATEGY_EXTENSION_POINT_NAME, + }); + server = await app.getServer(rest_1.RestServer); + } + function givenControllerInApp() { + const apispec = openapi_spec_builder_1.anOpenApiSpec() + .withOperation('get', '/whoAmI', { + 'x-operation-name': 'whoAmI', + responses: { + '200': { + description: '', + schema: { + type: 'string', + }, + }, + }, + }) + .build(); + let MyController = class MyController { + constructor(user) { + this.user = user; + } + async whoAmI() { + return this.user.id; + } + }; + __decorate([ + authentication_1.authenticate(AUTH_STRATEGY_NAME), + __metadata("design:type", Function), + __metadata("design:paramtypes", []), + __metadata("design:returntype", Promise) + ], MyController.prototype, "whoAmI", null); + MyController = __decorate([ + openapi_v3_1.api(apispec), + __param(0, context_1.inject(authentication_1.AuthenticationBindings.CURRENT_USER)), + __metadata("design:paramtypes", [Object]) + ], MyController); + app.controller(MyController); + } + function givenAuthenticatedSequence() { + let MySequence = class MySequence { + constructor(findRoute, parseParams, invoke, send, reject, authenticateRequest) { + this.findRoute = findRoute; + this.parseParams = parseParams; + this.invoke = invoke; + this.send = send; + this.reject = reject; + this.authenticateRequest = authenticateRequest; + } + async handle(context) { + try { + const { request, response } = context; + const route = this.findRoute(request); + // Authenticate + await this.authenticateRequest(request); + // Authentication successful, proceed to invoke controller + const args = await this.parseParams(request, route); + const result = await this.invoke(route, args); + this.send(response, result); + } + catch (error) { + this.reject(context, error); + return; + } + } + }; + MySequence = __decorate([ + __param(0, context_1.inject(SequenceActions.FIND_ROUTE)), + __param(1, context_1.inject(SequenceActions.PARSE_PARAMS)), + __param(2, context_1.inject(SequenceActions.INVOKE_METHOD)), + __param(3, context_1.inject(SequenceActions.SEND)), + __param(4, context_1.inject(SequenceActions.REJECT)), + __param(5, context_1.inject(authentication_1.AuthenticationBindings.AUTH_ACTION)), + __metadata("design:paramtypes", [Function, Function, Function, Function, Function, Function]) + ], MySequence); + // bind user defined sequence + server.sequence(MySequence); + } + function whenIMakeRequestTo(restServer) { + return testlab_1.createClientForHandler(restServer.requestHandler); + } +}); +class UserRepository { + constructor(list) { + this.list = list; + } + find(username, password, cb) { + const userList = this.list; + function search(key) { + return userList[key].profile.id === username; + } + const found = Object.keys(userList).find(search); + if (!found) + return cb(null, false); + if (userList[found].password !== password) + return cb(null, false); + cb(null, userList[found].profile); + } +} +//# sourceMappingURL=authentication-with-passport-strategy.acceptance.js.map \ No newline at end of file diff --git a/experimental/passport-adapter/dist/__tests__/acceptance/authentication-with-passport-strategy.acceptance.js.map b/experimental/passport-adapter/dist/__tests__/acceptance/authentication-with-passport-strategy.acceptance.js.map new file mode 100644 index 000000000000..31eeb6fe1819 --- /dev/null +++ b/experimental/passport-adapter/dist/__tests__/acceptance/authentication-with-passport-strategy.acceptance.js.map @@ -0,0 +1 @@ +{"version":3,"file":"authentication-with-passport-strategy.acceptance.js","sourceRoot":"","sources":["../../../src/__tests__/acceptance/authentication-with-passport-strategy.acceptance.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/experimental/passport-adapter/dist/__tests__/unit/fixtures/mock-passport-strategy.d.ts b/experimental/passport-adapter/dist/__tests__/unit/fixtures/mock-passport-strategy.d.ts new file mode 100644 index 000000000000..94d40d0209f4 --- /dev/null +++ b/experimental/passport-adapter/dist/__tests__/unit/fixtures/mock-passport-strategy.d.ts @@ -0,0 +1,28 @@ +import { Request } from '@loopback/rest'; +import { AuthenticateOptions, Strategy } from 'passport'; +import { UserProfile } from '@loopback/authentication'; +/** + * Test fixture for a mock asynchronous passport-strategy + */ +export declare class MockPassportStrategy extends Strategy { + private mockUser; + setMockUser(userObj: UserProfile): void; + /** + * authenticate() function similar to passport-strategy packages + * @param req + */ + authenticate(req: Request, options?: AuthenticateOptions): Promise; + /** + * @param req + * mock verification function; usually passed in as constructor argument for + * passport-strategy + * + * For the purpose of mock tests we have this here + * pass req.query.testState = 'fail' to mock failed authorization + * pass req.query.testState = 'error' to mock unexpected error + */ + verify(request: Request): Promise; + returnMockUser(): void; + returnUnauthorized(challenge?: string | number, status?: number): void; + returnError(err: string): void; +} diff --git a/experimental/passport-adapter/dist/__tests__/unit/fixtures/mock-passport-strategy.js b/experimental/passport-adapter/dist/__tests__/unit/fixtures/mock-passport-strategy.js new file mode 100644 index 000000000000..6fe8d2436bcf --- /dev/null +++ b/experimental/passport-adapter/dist/__tests__/unit/fixtures/mock-passport-strategy.js @@ -0,0 +1,57 @@ +"use strict"; +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/authentication +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT +Object.defineProperty(exports, "__esModule", { value: true }); +const passport_1 = require("passport"); +/** + * Test fixture for a mock asynchronous passport-strategy + */ +class MockPassportStrategy extends passport_1.Strategy { + setMockUser(userObj) { + this.mockUser = userObj; + } + /** + * authenticate() function similar to passport-strategy packages + * @param req + */ + async authenticate(req, options) { + await this.verify(req); + } + /** + * @param req + * mock verification function; usually passed in as constructor argument for + * passport-strategy + * + * For the purpose of mock tests we have this here + * pass req.query.testState = 'fail' to mock failed authorization + * pass req.query.testState = 'error' to mock unexpected error + */ + async verify(request) { + if (request.headers && + request.headers.testState && + request.headers.testState === 'fail') { + this.returnUnauthorized('authorization failed'); + return; + } + else if (request.headers && + request.headers.testState && + request.headers.testState === 'error') { + this.returnError('unexpected error'); + return; + } + process.nextTick(this.returnMockUser.bind(this)); + } + returnMockUser() { + this.success(this.mockUser); + } + returnUnauthorized(challenge, status) { + this.fail(challenge, status); + } + returnError(err) { + this.error(err); + } +} +exports.MockPassportStrategy = MockPassportStrategy; +//# sourceMappingURL=mock-passport-strategy.js.map \ No newline at end of file diff --git a/experimental/passport-adapter/dist/__tests__/unit/fixtures/mock-passport-strategy.js.map b/experimental/passport-adapter/dist/__tests__/unit/fixtures/mock-passport-strategy.js.map new file mode 100644 index 000000000000..5db71714c3f6 --- /dev/null +++ b/experimental/passport-adapter/dist/__tests__/unit/fixtures/mock-passport-strategy.js.map @@ -0,0 +1 @@ +{"version":3,"file":"mock-passport-strategy.js","sourceRoot":"","sources":["../../../../src/__tests__/unit/fixtures/mock-passport-strategy.ts"],"names":[],"mappings":";AAAA,iDAAiD;AACjD,wCAAwC;AACxC,+CAA+C;AAC/C,gEAAgE;;AAMhE,uCAAuD;AAGvD;;GAEG;AACH,MAAa,oBAAqB,SAAQ,mBAAQ;IAIhD,WAAW,CAAC,OAAoB;QAC9B,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;IAC1B,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,YAAY,CAAC,GAAY,EAAE,OAA6B;QAC5D,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;IACD;;;;;;;;OAQG;IACH,KAAK,CAAC,MAAM,CAAC,OAAgB;QAC3B,IACE,OAAO,CAAC,OAAO;YACf,OAAO,CAAC,OAAO,CAAC,SAAS;YACzB,OAAO,CAAC,OAAO,CAAC,SAAS,KAAK,MAAM,EACpC;YACA,IAAI,CAAC,kBAAkB,CAAC,sBAAsB,CAAC,CAAC;YAChD,OAAO;SACR;aAAM,IACL,OAAO,CAAC,OAAO;YACf,OAAO,CAAC,OAAO,CAAC,SAAS;YACzB,OAAO,CAAC,OAAO,CAAC,SAAS,KAAK,OAAO,EACrC;YACA,IAAI,CAAC,WAAW,CAAC,kBAAkB,CAAC,CAAC;YACrC,OAAO;SACR;QACD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACnD,CAAC;IAED,cAAc;QACZ,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC9B,CAAC;IAED,kBAAkB,CAAC,SAA2B,EAAE,MAAe;QAC7D,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAC/B,CAAC;IAED,WAAW,CAAC,GAAW;QACrB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,CAAC;CACF;AAtDD,oDAsDC"} \ No newline at end of file diff --git a/experimental/passport-adapter/dist/__tests__/unit/strategy-adapter.unit.d.ts b/experimental/passport-adapter/dist/__tests__/unit/strategy-adapter.unit.d.ts new file mode 100644 index 000000000000..cb0ff5c3b541 --- /dev/null +++ b/experimental/passport-adapter/dist/__tests__/unit/strategy-adapter.unit.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/experimental/passport-adapter/dist/__tests__/unit/strategy-adapter.unit.js b/experimental/passport-adapter/dist/__tests__/unit/strategy-adapter.unit.js new file mode 100644 index 000000000000..e2adb9c6acdc --- /dev/null +++ b/experimental/passport-adapter/dist/__tests__/unit/strategy-adapter.unit.js @@ -0,0 +1,70 @@ +"use strict"; +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/authentication +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT +Object.defineProperty(exports, "__esModule", { value: true }); +const rest_1 = require("@loopback/rest"); +const testlab_1 = require("@loopback/testlab"); +const mock_passport_strategy_1 = require("./fixtures/mock-passport-strategy"); +const __1 = require("../.."); +describe('Strategy Adapter', () => { + const mockUser = { name: 'user-name', id: 'mock-id' }; + describe('authenticate()', () => { + it('calls the authenticate method of the strategy', async () => { + let calledFlag = false; + // TODO: (as suggested by @bajtos) use sinon spy + class MyStrategy extends mock_passport_strategy_1.MockPassportStrategy { + // override authenticate method to set calledFlag + async authenticate(req, options) { + calledFlag = true; + await super.authenticate(req, options); + } + } + const strategy = new MyStrategy(); + const adapter = new __1.StrategyAdapter(strategy, 'mock-strategy'); + const request = {}; + await adapter.authenticate(request); + testlab_1.expect(calledFlag).to.be.true(); + }); + it('returns a promise which resolves to an object', async () => { + const strategy = new mock_passport_strategy_1.MockPassportStrategy(); + strategy.setMockUser(mockUser); + const adapter = new __1.StrategyAdapter(strategy, 'mock-strategy'); + const request = {}; + const user = await adapter.authenticate(request); + testlab_1.expect(user).to.be.eql(mockUser); + }); + it('throws Unauthorized error when authentication fails', async () => { + const strategy = new mock_passport_strategy_1.MockPassportStrategy(); + strategy.setMockUser(mockUser); + const adapter = new __1.StrategyAdapter(strategy, 'mock-strategy'); + const request = {}; + request.headers = { testState: 'fail' }; + let error; + try { + await adapter.authenticate(request); + } + catch (err) { + error = err; + } + testlab_1.expect(error).to.be.instanceof(rest_1.HttpErrors.Unauthorized); + }); + it('throws InternalServerError when strategy returns error', async () => { + const strategy = new mock_passport_strategy_1.MockPassportStrategy(); + strategy.setMockUser(mockUser); + const adapter = new __1.StrategyAdapter(strategy, 'mock-strategy'); + const request = {}; + request.headers = { testState: 'error' }; + let error; + try { + await adapter.authenticate(request); + } + catch (err) { + error = err; + } + testlab_1.expect(error).to.be.instanceof(rest_1.HttpErrors.InternalServerError); + }); + }); +}); +//# sourceMappingURL=strategy-adapter.unit.js.map \ No newline at end of file diff --git a/experimental/passport-adapter/dist/__tests__/unit/strategy-adapter.unit.js.map b/experimental/passport-adapter/dist/__tests__/unit/strategy-adapter.unit.js.map new file mode 100644 index 000000000000..18ebd1e6392e --- /dev/null +++ b/experimental/passport-adapter/dist/__tests__/unit/strategy-adapter.unit.js.map @@ -0,0 +1 @@ +{"version":3,"file":"strategy-adapter.unit.js","sourceRoot":"","sources":["../../../src/__tests__/unit/strategy-adapter.unit.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/experimental/passport-adapter/dist/index.d.ts b/experimental/passport-adapter/dist/index.d.ts new file mode 100644 index 000000000000..a7aaa74a7146 --- /dev/null +++ b/experimental/passport-adapter/dist/index.d.ts @@ -0,0 +1 @@ +export * from './strategy-adapter'; diff --git a/experimental/passport-adapter/dist/index.js b/experimental/passport-adapter/dist/index.js new file mode 100644 index 000000000000..294fec200aa2 --- /dev/null +++ b/experimental/passport-adapter/dist/index.js @@ -0,0 +1,7 @@ +"use strict"; +function __export(m) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +} +Object.defineProperty(exports, "__esModule", { value: true }); +__export(require("./strategy-adapter")); +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/experimental/passport-adapter/dist/index.js.map b/experimental/passport-adapter/dist/index.js.map new file mode 100644 index 000000000000..2c21a571efc7 --- /dev/null +++ b/experimental/passport-adapter/dist/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;AAAA,wCAAmC"} \ No newline at end of file diff --git a/experimental/passport-adapter/dist/strategy-adapter.d.ts b/experimental/passport-adapter/dist/strategy-adapter.d.ts new file mode 100644 index 000000000000..2ac81ba10066 --- /dev/null +++ b/experimental/passport-adapter/dist/strategy-adapter.d.ts @@ -0,0 +1,29 @@ +import { Request } from '@loopback/rest'; +import { Strategy } from 'passport'; +import { AuthenticationStrategy, UserProfile } from '@loopback/authentication'; +/** + * Adapter class to invoke passport-strategy + * 1. provides express dependencies to the passport strategies + * 2. provides shimming of requests for passport authentication + * 3. provides lifecycle similar to express to the passport-strategy + * 3. provides state methods to the strategy instance + * see: https://github.com/jaredhanson/passport + */ +export declare class StrategyAdapter implements AuthenticationStrategy { + private readonly strategy; + readonly name: string; + passportStrategy: Strategy; + /** + * @param strategy instance of a class which implements a passport-strategy; + * @description http://passportjs.org/ + */ + constructor(strategy: Strategy, name: string); + /** + * The function to invoke the contained passport strategy. + * 1. Create an instance of the strategy + * 2. add success and failure state handlers + * 3. authenticate using the strategy + * @param request The incoming request. + */ + authenticate(request: Request): Promise; +} diff --git a/experimental/passport-adapter/dist/strategy-adapter.js b/experimental/passport-adapter/dist/strategy-adapter.js new file mode 100644 index 000000000000..5d33b00bc0c9 --- /dev/null +++ b/experimental/passport-adapter/dist/strategy-adapter.js @@ -0,0 +1,62 @@ +"use strict"; +// Copyright IBM Corp. 2017,2019. All Rights Reserved. +// Node module: @loopback/authentication +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT +Object.defineProperty(exports, "__esModule", { value: true }); +const rest_1 = require("@loopback/rest"); +const passportRequestMixin = require('passport/lib/http/request'); +/** + * Adapter class to invoke passport-strategy + * 1. provides express dependencies to the passport strategies + * 2. provides shimming of requests for passport authentication + * 3. provides lifecycle similar to express to the passport-strategy + * 3. provides state methods to the strategy instance + * see: https://github.com/jaredhanson/passport + */ +class StrategyAdapter { + /** + * @param strategy instance of a class which implements a passport-strategy; + * @description http://passportjs.org/ + */ + constructor(strategy, name) { + this.strategy = strategy; + this.name = name; + this.name = name; + this.passportStrategy = strategy; + } + /** + * The function to invoke the contained passport strategy. + * 1. Create an instance of the strategy + * 2. add success and failure state handlers + * 3. authenticate using the strategy + * @param request The incoming request. + */ + authenticate(request) { + return new Promise((resolve, reject) => { + // mix-in passport additions like req.logIn and req.logOut + for (const key in passportRequestMixin) { + // tslint:disable-next-line:no-any + request[key] = passportRequestMixin[key]; + } + // create a prototype chain of an instance of a passport strategy + const strategy = Object.create(this.strategy); + // add success state handler to strategy instance + strategy.success = function (user) { + resolve(user); + }; + // add failure state handler to strategy instance + strategy.fail = function (challenge) { + reject(new rest_1.HttpErrors.Unauthorized(challenge)); + }; + // add error state handler to strategy instance + strategy.error = function (error) { + reject(new rest_1.HttpErrors.InternalServerError(error)); + }; + // authenticate + strategy.authenticate(request); + }); + } +} +exports.StrategyAdapter = StrategyAdapter; +//# sourceMappingURL=strategy-adapter.js.map \ No newline at end of file diff --git a/experimental/passport-adapter/dist/strategy-adapter.js.map b/experimental/passport-adapter/dist/strategy-adapter.js.map new file mode 100644 index 000000000000..deed2e2004d9 --- /dev/null +++ b/experimental/passport-adapter/dist/strategy-adapter.js.map @@ -0,0 +1 @@ +{"version":3,"file":"strategy-adapter.js","sourceRoot":"","sources":["../src/strategy-adapter.ts"],"names":[],"mappings":";AAAA,sDAAsD;AACtD,wCAAwC;AACxC,+CAA+C;AAC/C,gEAAgE;;AAEhE,yCAAmD;AAInD,MAAM,oBAAoB,GAAG,OAAO,CAAC,2BAA2B,CAAC,CAAC;AAElE;;;;;;;GAOG;AACH,MAAa,eAAe;IAE1B;;;OAGG;IACH,YAA6B,QAAkB,EAAW,IAAY;QAAzC,aAAQ,GAAR,QAAQ,CAAU;QAAW,SAAI,GAAJ,IAAI,CAAQ;QACpE,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,gBAAgB,GAAG,QAAQ,CAAC;IACnC,CAAC;IAED;;;;;;OAMG;IACH,YAAY,CAAC,OAAgB;QAC3B,OAAO,IAAI,OAAO,CAAc,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAClD,0DAA0D;YAC1D,KAAK,MAAM,GAAG,IAAI,oBAAoB,EAAE;gBACtC,kCAAkC;gBACjC,OAAe,CAAC,GAAG,CAAC,GAAG,oBAAoB,CAAC,GAAG,CAAC,CAAC;aACnD;YAED,iEAAiE;YACjE,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAE9C,iDAAiD;YACjD,QAAQ,CAAC,OAAO,GAAG,UAAS,IAAiB;gBAC3C,OAAO,CAAC,IAAI,CAAC,CAAC;YAChB,CAAC,CAAC;YAEF,iDAAiD;YACjD,QAAQ,CAAC,IAAI,GAAG,UAAS,SAAiB;gBACxC,MAAM,CAAC,IAAI,iBAAU,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC;YACjD,CAAC,CAAC;YAEF,+CAA+C;YAC/C,QAAQ,CAAC,KAAK,GAAG,UAAS,KAAa;gBACrC,MAAM,CAAC,IAAI,iBAAU,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC,CAAC;YACpD,CAAC,CAAC;YAEF,eAAe;YACf,QAAQ,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;QACjC,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AAhDD,0CAgDC"} \ No newline at end of file diff --git a/experimental/passport-adapter/docs.json b/experimental/passport-adapter/docs.json new file mode 100644 index 000000000000..30b1a4b8d345 --- /dev/null +++ b/experimental/passport-adapter/docs.json @@ -0,0 +1,8 @@ +{ + "content": [ + "index.ts", + "src/index.ts", + "src/strategy-adapter.ts" + ], + "codeSectionDepth": 4 +} diff --git a/experimental/passport-adapter/index.d.ts b/experimental/passport-adapter/index.d.ts new file mode 100644 index 000000000000..1318d745dd73 --- /dev/null +++ b/experimental/passport-adapter/index.d.ts @@ -0,0 +1,6 @@ +// Copyright IBM Corp. 2017,2018. All Rights Reserved. +// Node module: @loopback/metadata +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +export * from './dist'; diff --git a/experimental/passport-adapter/index.js b/experimental/passport-adapter/index.js new file mode 100644 index 000000000000..abe1a945f2d7 --- /dev/null +++ b/experimental/passport-adapter/index.js @@ -0,0 +1,6 @@ +// Copyright IBM Corp. 2017,2018. All Rights Reserved. +// Node module: @loopback/metadata +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +module.exports = require('./dist'); diff --git a/experimental/passport-adapter/index.ts b/experimental/passport-adapter/index.ts new file mode 100644 index 000000000000..04733da75432 --- /dev/null +++ b/experimental/passport-adapter/index.ts @@ -0,0 +1,8 @@ +// Copyright IBM Corp. 2017,2018. All Rights Reserved. +// Node module: @loopback/metadata +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +// DO NOT EDIT THIS FILE +// Add any additional (re)exports to src/index.ts instead. +export * from './src'; diff --git a/experimental/passport-adapter/package.json b/experimental/passport-adapter/package.json new file mode 100644 index 000000000000..43cdeb750112 --- /dev/null +++ b/experimental/passport-adapter/package.json @@ -0,0 +1,60 @@ +{ + "name": "@loopback/passport-adapter", + "version": "0.1.0", + "description": "A package creating adapters between the passport module and @loopback/authentication", + "engines": { + "node": ">=8.9" + }, + "scripts": { + "acceptance": "lb-mocha \"dist/__tests__/acceptance/**/*.js\"", + "build:apidocs": "lb-apidocs", + "build": "lb-tsc es2017 --outDir dist", + "clean": "lb-clean loopback-passport-adapter*.tgz dist package api-docs", + "pretest": "npm run build", + "test": "lb-mocha \"dist/__tests__/**/*.js\"", + "unit": "lb-mocha \"dist/__tests__/unit/**/*.js\"", + "verify": "npm pack && tar xf loopback-passport-adapter*.tgz && tree package && npm run clean" + }, + "author": "IBM Corp.", + "copyright.owner": "IBM Corp.", + "license": "MIT", + "keywords": [ + "Passport", + "Authentication", + "TypeScript" + ], + "files": [ + "README.md", + "index.js", + "index.d.ts", + "dist", + "src", + "!*/__tests__" + ], + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/strongloop/loopback-next.git" + }, + "dependencies": { + "@loopback/authentication": "^2.0.0", + "@loopback/rest": "^1.11.2", + "@loopback/core": "^1.7.1", + "@loopback/context": "^1.16.0", + "@loopback/metadata": "^1.1.5", + "@loopback/openapi-v3": "^1.4.0", + "passport": "^0.4.0" + }, + "devDependencies": { + "@loopback/build": "^1.5.5", + "@loopback/openapi-spec-builder": "^1.1.11", + "@loopback/testlab": "^1.2.10", + "@loopback/tslint-config": "^2.0.4", + "@types/node": "^10.11.2", + "@types/passport": "^1.0.0", + "@types/passport-http": "^0.3.8", + "passport-http": "^0.3.0" + } +} diff --git a/experimental/passport-adapter/src/__tests__/acceptance/authentication-with-passport-strategy.acceptance.ts b/experimental/passport-adapter/src/__tests__/acceptance/authentication-with-passport-strategy.acceptance.ts new file mode 100644 index 000000000000..57e3b830e0cf --- /dev/null +++ b/experimental/passport-adapter/src/__tests__/acceptance/authentication-with-passport-strategy.acceptance.ts @@ -0,0 +1,214 @@ +// Copyright IBM Corp. 2019. 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 {inject} from '@loopback/context'; +import {addExtension, Application, Provider} from '@loopback/core'; +import {anOpenApiSpec} from '@loopback/openapi-spec-builder'; +import {api, get} from '@loopback/openapi-v3'; +import { + FindRoute, + InvokeMethod, + ParseParams, + Reject, + RequestContext, + RestBindings, + RestComponent, + RestServer, + Send, + SequenceHandler, +} from '@loopback/rest'; +import {Client, createClientForHandler} from '@loopback/testlab'; +import {BasicStrategy, BasicVerifyFunction} from 'passport-http'; +import { + authenticate, + AuthenticationStrategy, + AuthenticateFn, + AuthenticationBindings, + AuthenticationComponent, + UserProfile, +} from '@loopback/authentication'; +import {StrategyAdapter} from '../../'; +const SequenceActions = RestBindings.SequenceActions; +const AUTH_STRATEGY_NAME = 'basic'; + +describe('Basic Authentication', () => { + let app: Application; + let server: RestServer; + let users: UserRepository; + beforeEach(givenAServer); + beforeEach(givenUserRepository); + beforeEach(givenControllerInApp); + beforeEach(givenAuthenticatedSequence); + + it('authenticates successfully for correct credentials', async () => { + const client = whenIMakeRequestTo(server); + const credential = + users.list.joe.profile.id + ':' + users.list.joe.password; + const hash = Buffer.from(credential).toString('base64'); + await client + .get('/whoAmI') + .set('Authorization', 'Basic ' + hash) + .expect(users.list.joe.profile.id); + }); + + it('returns error for invalid credentials', async () => { + const client = whenIMakeRequestTo(server); + const credential = users.list.Simpson.profile.id + ':' + 'invalid'; + const hash = Buffer.from(credential).toString('base64'); + await client + .get('/whoAmI') + .set('Authorization', 'Basic ' + hash) + .expect(401); + }); + + it('allows anonymous requests to methods with no decorator', async () => { + class InfoController { + @get('/status') + status() { + return {running: true}; + } + } + + app.controller(InfoController); + await whenIMakeRequestTo(server) + .get('/status') + .expect(200, {running: true}); + }); + + function givenUserRepository() { + users = new UserRepository({ + joe: {profile: {id: 'joe'}, password: '12345'}, + Simpson: {profile: {id: 'sim123'}, password: 'alpha'}, + Flinstone: {profile: {id: 'Flint'}, password: 'beta'}, + George: {profile: {id: 'Curious'}, password: 'gamma'}, + }); + } + + // Since it has to be user's job to provide the `verify` function and + // instantiate the passport strategy, we cannot add the imported `BasicStrategy` + // class as extension directly, we need to wrap it as a strategy provider, + // then add the provider class as the extension. + // See Line 89 in the function `givenAServer` + class PassportBasicAuthProvider implements Provider { + value(): AuthenticationStrategy { + const basicStrategy = this.configuredBasicStrategy(verify); + return this.convertToAuthStrategy(basicStrategy); + } + + configuredBasicStrategy(verifyFn: BasicVerifyFunction): BasicStrategy { + return new BasicStrategy(verifyFn); + } + + convertToAuthStrategy(basic: BasicStrategy): AuthenticationStrategy { + return new StrategyAdapter(basic, AUTH_STRATEGY_NAME); + } + } + + function verify(username: string, password: string, cb: Function) { + process.nextTick(() => { + users.find(username, password, cb); + }); + } + + async function givenAServer() { + app = new Application(); + app.component(AuthenticationComponent); + app.component(RestComponent); + addExtension( + app, + AuthenticationBindings.AUTHENTICATION_STRATEGY_EXTENSION_POINT_NAME, + PassportBasicAuthProvider, + { + namespace: + AuthenticationBindings.AUTHENTICATION_STRATEGY_EXTENSION_POINT_NAME, + }, + ); + server = await app.getServer(RestServer); + } + + function givenControllerInApp() { + const apispec = anOpenApiSpec() + .withOperation('get', '/whoAmI', { + 'x-operation-name': 'whoAmI', + responses: { + '200': { + description: '', + schema: { + type: 'string', + }, + }, + }, + }) + .build(); + + @api(apispec) + class MyController { + constructor( + @inject(AuthenticationBindings.CURRENT_USER) private user: UserProfile, + ) {} + + @authenticate(AUTH_STRATEGY_NAME) + async whoAmI(): Promise { + return this.user.id; + } + } + app.controller(MyController); + } + + function givenAuthenticatedSequence() { + class MySequence implements SequenceHandler { + constructor( + @inject(SequenceActions.FIND_ROUTE) protected findRoute: FindRoute, + @inject(SequenceActions.PARSE_PARAMS) + protected parseParams: ParseParams, + @inject(SequenceActions.INVOKE_METHOD) protected invoke: InvokeMethod, + @inject(SequenceActions.SEND) protected send: Send, + @inject(SequenceActions.REJECT) protected reject: Reject, + @inject(AuthenticationBindings.AUTH_ACTION) + protected authenticateRequest: AuthenticateFn, + ) {} + + async handle(context: RequestContext) { + try { + const {request, response} = context; + const route = this.findRoute(request); + + // Authenticate + await this.authenticateRequest(request); + + // Authentication successful, proceed to invoke controller + const args = await this.parseParams(request, route); + const result = await this.invoke(route, args); + this.send(response, result); + } catch (error) { + this.reject(context, error); + return; + } + } + } + // bind user defined sequence + server.sequence(MySequence); + } + + function whenIMakeRequestTo(restServer: RestServer): Client { + return createClientForHandler(restServer.requestHandler); + } +}); + +class UserRepository { + constructor( + readonly list: {[key: string]: {profile: UserProfile; password: string}}, + ) {} + find(username: string, password: string, cb: Function): void { + const userList = this.list; + function search(key: string) { + return userList[key].profile.id === username; + } + const found = Object.keys(userList).find(search); + if (!found) return cb(null, false); + if (userList[found].password !== password) return cb(null, false); + cb(null, userList[found].profile); + } +} diff --git a/experimental/passport-adapter/src/__tests__/unit/fixtures/mock-passport-strategy.ts b/experimental/passport-adapter/src/__tests__/unit/fixtures/mock-passport-strategy.ts new file mode 100644 index 000000000000..e5f9761ec24b --- /dev/null +++ b/experimental/passport-adapter/src/__tests__/unit/fixtures/mock-passport-strategy.ts @@ -0,0 +1,70 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/authentication +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +// Should it be imported from 'express'? +// The `Request` type from 'express' is not compatible +// with the one from `@loopback/rest` now. +import {Request} from '@loopback/rest'; +import {AuthenticateOptions, Strategy} from 'passport'; +import {UserProfile} from '@loopback/authentication'; + +/** + * Test fixture for a mock asynchronous passport-strategy + */ +export class MockPassportStrategy extends Strategy { + // user to return for successful authentication + private mockUser: UserProfile; + + setMockUser(userObj: UserProfile) { + this.mockUser = userObj; + } + + /** + * authenticate() function similar to passport-strategy packages + * @param req + */ + async authenticate(req: Request, options?: AuthenticateOptions) { + await this.verify(req); + } + /** + * @param req + * mock verification function; usually passed in as constructor argument for + * passport-strategy + * + * For the purpose of mock tests we have this here + * pass req.query.testState = 'fail' to mock failed authorization + * pass req.query.testState = 'error' to mock unexpected error + */ + async verify(request: Request) { + if ( + request.headers && + request.headers.testState && + request.headers.testState === 'fail' + ) { + this.returnUnauthorized('authorization failed'); + return; + } else if ( + request.headers && + request.headers.testState && + request.headers.testState === 'error' + ) { + this.returnError('unexpected error'); + return; + } + process.nextTick(this.returnMockUser.bind(this)); + } + + returnMockUser() { + this.success(this.mockUser); + } + + returnUnauthorized(challenge?: string | number, status?: number) { + this.fail(challenge, status); + } + + returnError(err: string) { + this.error(err); + } +} diff --git a/experimental/passport-adapter/src/__tests__/unit/strategy-adapter.unit.ts b/experimental/passport-adapter/src/__tests__/unit/strategy-adapter.unit.ts new file mode 100644 index 000000000000..98a25f2dc1c8 --- /dev/null +++ b/experimental/passport-adapter/src/__tests__/unit/strategy-adapter.unit.ts @@ -0,0 +1,73 @@ +// Copyright IBM Corp. 2019. 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 {UserProfile} from '@loopback/authentication'; +import {HttpErrors, Request} from '@loopback/rest'; +import {expect} from '@loopback/testlab'; +import {AuthenticateOptions} from 'passport'; +import {MockPassportStrategy} from './fixtures/mock-passport-strategy'; +import {StrategyAdapter} from '../..'; + +describe('Strategy Adapter', () => { + const mockUser: UserProfile = {name: 'user-name', id: 'mock-id'}; + + describe('authenticate()', () => { + it('calls the authenticate method of the strategy', async () => { + let calledFlag = false; + // TODO: (as suggested by @bajtos) use sinon spy + class MyStrategy extends MockPassportStrategy { + // override authenticate method to set calledFlag + async authenticate(req: Request, options?: AuthenticateOptions) { + calledFlag = true; + await super.authenticate(req, options); + } + } + const strategy = new MyStrategy(); + const adapter = new StrategyAdapter(strategy, 'mock-strategy'); + const request = {}; + await adapter.authenticate(request); + expect(calledFlag).to.be.true(); + }); + + it('returns a promise which resolves to an object', async () => { + const strategy = new MockPassportStrategy(); + strategy.setMockUser(mockUser); + const adapter = new StrategyAdapter(strategy, 'mock-strategy'); + const request = {}; + const user: Object = await adapter.authenticate(request); + expect(user).to.be.eql(mockUser); + }); + + it('throws Unauthorized error when authentication fails', async () => { + const strategy = new MockPassportStrategy(); + strategy.setMockUser(mockUser); + const adapter = new StrategyAdapter(strategy, 'mock-strategy'); + const request = {}; + request.headers = {testState: 'fail'}; + let error; + try { + await adapter.authenticate(request); + } catch (err) { + error = err; + } + expect(error).to.be.instanceof(HttpErrors.Unauthorized); + }); + + it('throws InternalServerError when strategy returns error', async () => { + const strategy = new MockPassportStrategy(); + strategy.setMockUser(mockUser); + const adapter = new StrategyAdapter(strategy, 'mock-strategy'); + const request = {}; + request.headers = {testState: 'error'}; + let error; + try { + await adapter.authenticate(request); + } catch (err) { + error = err; + } + expect(error).to.be.instanceof(HttpErrors.InternalServerError); + }); + }); +}); diff --git a/experimental/passport-adapter/src/index.ts b/experimental/passport-adapter/src/index.ts new file mode 100644 index 000000000000..a7aaa74a7146 --- /dev/null +++ b/experimental/passport-adapter/src/index.ts @@ -0,0 +1 @@ +export * from './strategy-adapter'; diff --git a/experimental/passport-adapter/src/strategy-adapter.ts b/experimental/passport-adapter/src/strategy-adapter.ts new file mode 100644 index 000000000000..edcbec6bdfd4 --- /dev/null +++ b/experimental/passport-adapter/src/strategy-adapter.ts @@ -0,0 +1,68 @@ +// Copyright IBM Corp. 2017,2019. 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 {HttpErrors, Request} from '@loopback/rest'; +import {Strategy} from 'passport'; +import {AuthenticationStrategy, UserProfile} from '@loopback/authentication'; + +const passportRequestMixin = require('passport/lib/http/request'); + +/** + * Adapter class to invoke passport-strategy + * 1. provides express dependencies to the passport strategies + * 2. provides shimming of requests for passport authentication + * 3. provides lifecycle similar to express to the passport-strategy + * 3. provides state methods to the strategy instance + * see: https://github.com/jaredhanson/passport + */ +export class StrategyAdapter implements AuthenticationStrategy { + passportStrategy: Strategy; + /** + * @param strategy instance of a class which implements a passport-strategy; + * @description http://passportjs.org/ + */ + constructor(private readonly strategy: Strategy, readonly name: string) { + this.name = name; + this.passportStrategy = strategy; + } + + /** + * The function to invoke the contained passport strategy. + * 1. Create an instance of the strategy + * 2. add success and failure state handlers + * 3. authenticate using the strategy + * @param request The incoming request. + */ + authenticate(request: Request): Promise { + return new Promise((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); + + // add success state handler to strategy instance + strategy.success = function(user: UserProfile) { + resolve(user); + }; + + // add failure state handler to strategy instance + strategy.fail = function(challenge: string) { + reject(new HttpErrors.Unauthorized(challenge)); + }; + + // add error state handler to strategy instance + strategy.error = function(error: string) { + reject(new HttpErrors.InternalServerError(error)); + }; + + // authenticate + strategy.authenticate(request); + }); + } +} diff --git a/experimental/passport-adapter/tsconfig.build.json b/experimental/passport-adapter/tsconfig.build.json new file mode 100644 index 000000000000..5e72e589a51a --- /dev/null +++ b/experimental/passport-adapter/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "$schema": "http://json.schemastore.org/tsconfig", + "extends": "@loopback/build/config/tsconfig.common.json", + "compilerOptions": { + "rootDir": "src", + }, + "include": ["src"] +} diff --git a/lerna.json b/lerna.json index 8c8b9ef7efa5..3aa19b09cb13 100644 --- a/lerna.json +++ b/lerna.json @@ -5,6 +5,7 @@ "docs", "examples/*", "packages/*", + "experimental/*", "sandbox/*" ], "command": { diff --git a/package.json b/package.json index 8fce850d9ce6..f91dbb76cdab 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "lint:fix": "npm run tslint:fix && npm run prettier:fix", "tslint": "node packages/build/bin/run-tslint --project tsconfig.json", "tslint:fix": "npm run tslint -- --fix", - "prettier:cli": "node packages/build/bin/run-prettier \"**/*.ts\" \"**/*.js\" \"**/*.md\"", + "prettier:cli": "node packages/build/bin/run-prettier \"experimental/**/*.ts\" \"experimental/**/*.js\" \"experimental/*.md\"", "prettier:check": "npm run prettier:cli -- -l", "prettier:fix": "npm run prettier:cli -- --write", "clean": "lerna run clean && node packages/build/bin/run-clean \"packages/*/dist\" \"examples/*/dist\" \"benchmark/dist\"", @@ -54,8 +54,9 @@ "test:ci": "node packages/build/bin/run-nyc npm run mocha --scripts-prepend-node-path", "verify:docs": "npm run build:site -- --verify", "build:site": "./bin/build-docs-site.sh", - "mocha": "node packages/build/bin/run-mocha \"packages/*/dist/__tests__/**/*.js\" \"examples/*/dist/__tests__/**/*.js\" \"packages/cli/test/**/*.js\" \"packages/build/test/*/*.js\"", - "posttest": "npm run lint" + "mocha": "node packages/build/bin/run-mocha \"experimental/*/dist/__tests__/**/*.js\"", + "posttest": "npm run lint", + "private": "node ./bin/mark-non-lab-packages-private.js" }, "config": { "commitizen": { diff --git a/packages/authentication/package.json b/packages/authentication/package.json index 97051104e953..afdb942705a4 100644 --- a/packages/authentication/package.json +++ b/packages/authentication/package.json @@ -54,5 +54,6 @@ "repository": { "type": "git", "url": "https://github.com/strongloop/loopback-next.git" - } + }, + "private": true } diff --git a/packages/boot/package.json b/packages/boot/package.json index 8e4f344ef70a..ac45eafa165b 100644 --- a/packages/boot/package.json +++ b/packages/boot/package.json @@ -51,5 +51,6 @@ "repository": { "type": "git", "url": "https://github.com/strongloop/loopback-next.git" - } + }, + "private": true } diff --git a/packages/booter-lb3app/package.json b/packages/booter-lb3app/package.json index e03648502819..c23dfe027aa1 100644 --- a/packages/booter-lb3app/package.json +++ b/packages/booter-lb3app/package.json @@ -65,5 +65,6 @@ "repository": { "type": "git", "url": "https://github.com/strongloop/loopback-next.git" - } + }, + "private": true } diff --git a/packages/build/package.json b/packages/build/package.json index f9a3701cbdcc..dbae244e8412 100644 --- a/packages/build/package.json +++ b/packages/build/package.json @@ -43,5 +43,6 @@ "scripts": { "test": "npm run mocha", "mocha": "node bin/run-mocha --timeout 30000 \"test/integration/*.js\"" - } + }, + "private": true } diff --git a/packages/cli/package.json b/packages/cli/package.json index bc34a1f22015..636100b53090 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -125,5 +125,6 @@ } }, "copyright.owner": "IBM Corp.", - "license": "MIT" + "license": "MIT", + "private": true } diff --git a/packages/context/package.json b/packages/context/package.json index c2650dbfdd4c..6d38900d9dc9 100644 --- a/packages/context/package.json +++ b/packages/context/package.json @@ -54,5 +54,6 @@ "repository": { "type": "git", "url": "https://github.com/strongloop/loopback-next.git" - } + }, + "private": true } diff --git a/packages/core/package.json b/packages/core/package.json index 65ffaf4c862d..532f521cbd26 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -41,5 +41,6 @@ "repository": { "type": "git", "url": "https://github.com/strongloop/loopback-next.git" - } + }, + "private": true } diff --git a/packages/http-caching-proxy/package.json b/packages/http-caching-proxy/package.json index 4e1f8a1f0e58..c417e79c6725 100644 --- a/packages/http-caching-proxy/package.json +++ b/packages/http-caching-proxy/package.json @@ -55,5 +55,6 @@ "repository": { "type": "git", "url": "https://github.com/strongloop/loopback-next.git" - } + }, + "private": true } diff --git a/packages/http-server/package.json b/packages/http-server/package.json index d19400dca465..cafa83ab85ae 100644 --- a/packages/http-server/package.json +++ b/packages/http-server/package.json @@ -40,5 +40,6 @@ "repository": { "type": "git", "url": "https://github.com/strongloop/loopback-next.git" - } + }, + "private": true } diff --git a/packages/metadata/package.json b/packages/metadata/package.json index bd148332d39b..36afdef6f450 100644 --- a/packages/metadata/package.json +++ b/packages/metadata/package.json @@ -50,5 +50,6 @@ "repository": { "type": "git", "url": "https://github.com/strongloop/loopback-next.git" - } + }, + "private": true } diff --git a/packages/openapi-spec-builder/package.json b/packages/openapi-spec-builder/package.json index 45eb2c45283c..0f7c4d08d0a7 100644 --- a/packages/openapi-spec-builder/package.json +++ b/packages/openapi-spec-builder/package.json @@ -43,5 +43,6 @@ "repository": { "type": "git", "url": "https://github.com/strongloop/loopback-next.git" - } + }, + "private": true } diff --git a/packages/openapi-v3-types/package.json b/packages/openapi-v3-types/package.json index 09791e91986b..ba761851940d 100644 --- a/packages/openapi-v3-types/package.json +++ b/packages/openapi-v3-types/package.json @@ -45,5 +45,6 @@ "repository": { "type": "git", "url": "https://github.com/strongloop/loopback-next.git" - } + }, + "private": true } diff --git a/packages/openapi-v3/package.json b/packages/openapi-v3/package.json index 5c331c07de7f..df2deb5b45a4 100644 --- a/packages/openapi-v3/package.json +++ b/packages/openapi-v3/package.json @@ -54,5 +54,6 @@ "@loopback/repository-json-schema": "^1.4.8", "debug": "^4.0.1", "lodash": "^4.17.11" - } + }, + "private": true } diff --git a/packages/repository-json-schema/package.json b/packages/repository-json-schema/package.json index 6537bac50fec..96194ace87f3 100644 --- a/packages/repository-json-schema/package.json +++ b/packages/repository-json-schema/package.json @@ -47,5 +47,6 @@ "repository": { "type": "git", "url": "https://github.com/strongloop/loopback-next.git" - } + }, + "private": true } diff --git a/packages/repository/package.json b/packages/repository/package.json index 662ac9121dd8..3010a56fae94 100644 --- a/packages/repository/package.json +++ b/packages/repository/package.json @@ -44,5 +44,6 @@ "repository": { "type": "git", "url": "https://github.com/strongloop/loopback-next.git" - } + }, + "private": true } diff --git a/packages/rest-explorer/package.json b/packages/rest-explorer/package.json index 350a91c9a7da..daa617ba0859 100644 --- a/packages/rest-explorer/package.json +++ b/packages/rest-explorer/package.json @@ -52,5 +52,6 @@ "repository": { "type": "git", "url": "https://github.com/strongloop/loopback-next.git" - } + }, + "private": true } diff --git a/packages/rest/package.json b/packages/rest/package.json index 1ab211b525a5..b0c65d0460ef 100644 --- a/packages/rest/package.json +++ b/packages/rest/package.json @@ -75,5 +75,6 @@ "repository": { "type": "git", "url": "https://github.com/strongloop/loopback-next.git" - } + }, + "private": true } diff --git a/packages/service-proxy/package.json b/packages/service-proxy/package.json index 5697cb990c75..cf10bfa77a13 100644 --- a/packages/service-proxy/package.json +++ b/packages/service-proxy/package.json @@ -45,5 +45,6 @@ "repository": { "type": "git", "url": "https://github.com/strongloop/loopback-next.git" - } + }, + "private": true } diff --git a/packages/testlab/package.json b/packages/testlab/package.json index e70698de544f..a39504f844fa 100644 --- a/packages/testlab/package.json +++ b/packages/testlab/package.json @@ -48,5 +48,6 @@ "repository": { "type": "git", "url": "https://github.com/strongloop/loopback-next.git" - } + }, + "private": true } diff --git a/packages/tslint-config/package.json b/packages/tslint-config/package.json index a8c617307e0c..097af090f109 100644 --- a/packages/tslint-config/package.json +++ b/packages/tslint-config/package.json @@ -25,5 +25,6 @@ }, "publishConfig": { "access": "public" - } + }, + "private": true }