From 226859c79ca641e2626c93de02bfb47055a0ba85 Mon Sep 17 00:00:00 2001 From: David Luecke Date: Mon, 11 Feb 2019 07:33:16 -0800 Subject: [PATCH] feat: Authentication v3 local authentication (#1211) BREAKING CHANGE: Update authentication strategies for @feathersjs/authentication v3 --- package.json | 2 +- packages/authentication-jwt/.npmignore | 1 - packages/authentication-jwt/CHANGELOG.md | 233 --------- packages/authentication-jwt/LICENSE | 22 - packages/authentication-jwt/README.md | 47 -- packages/authentication-jwt/lib/index.js | 104 ---- packages/authentication-jwt/lib/verifier.js | 40 -- packages/authentication-jwt/package.json | 58 --- .../authentication-jwt/test/index.test.js | 472 ------------------ .../test/integration.test.js | 88 ---- .../authentication-jwt/test/verifier.test.js | 142 ------ .../lib/hooks/hash-password.js | 71 ++- .../authentication-local/lib/hooks/index.js | 7 - packages/authentication-local/lib/index.js | 74 +-- packages/authentication-local/lib/strategy.js | 123 +++++ .../authentication-local/lib/utils/hash.js | 27 - packages/authentication-local/lib/verifier.js | 103 ---- packages/authentication-local/package.json | 13 +- packages/authentication-local/test/fixture.js | 38 ++ .../test/hooks/hash-password.test.js | 209 +++----- .../test/hooks/index.test.js | 16 - .../test/hooks/protect.test.js | 25 +- .../authentication-local/test/index.test.js | 204 -------- .../test/integration.test.js | 62 --- .../test/strategy.test.js | 133 +++++ .../test/verifier.test.js | 305 ----------- packages/authentication-oauth1/.npmignore | 1 - packages/authentication-oauth1/CHANGELOG.md | 208 -------- packages/authentication-oauth1/LICENSE | 22 - packages/authentication-oauth1/README.md | 62 --- .../lib/express/error-handler.js | 11 - .../lib/express/handler.js | 34 -- packages/authentication-oauth1/lib/index.js | 113 ----- .../authentication-oauth1/lib/verifier.js | 120 ----- packages/authentication-oauth1/package.json | 57 --- .../test/express/error-handler.test.js | 54 -- .../test/express/handler.test.js | 96 ---- .../test/fixtures/strategy.js | 41 -- .../authentication-oauth1/test/index.test.js | 290 ----------- .../test/verifier.test.js | 331 ------------ packages/authentication-oauth2/.npmignore | 1 - packages/authentication-oauth2/CHANGELOG.md | 310 ------------ packages/authentication-oauth2/LICENSE | 22 - packages/authentication-oauth2/README.md | 57 --- .../lib/express/error-handler.js | 11 - .../lib/express/handler.js | 34 -- packages/authentication-oauth2/lib/index.js | 125 ----- .../authentication-oauth2/lib/verifier.js | 122 ----- packages/authentication-oauth2/package.json | 56 --- .../test/express/error-handler.test.js | 54 -- .../test/express/handler.test.js | 96 ---- .../test/fixtures/strategy.js | 41 -- .../authentication-oauth2/test/index.test.js | 291 ----------- .../test/verifier.test.js | 331 ------------ packages/authentication/lib/core.js | 8 +- packages/authentication/lib/index.js | 2 - packages/authentication/lib/jwt.js | 35 +- packages/authentication/lib/options.js | 6 +- packages/authentication/lib/strategy.js | 17 - packages/authentication/test/core.test.js | 4 +- packages/authentication/test/jwt.test.js | 10 + 61 files changed, 456 insertions(+), 5236 deletions(-) delete mode 100644 packages/authentication-jwt/.npmignore delete mode 100644 packages/authentication-jwt/CHANGELOG.md delete mode 100644 packages/authentication-jwt/LICENSE delete mode 100644 packages/authentication-jwt/README.md delete mode 100644 packages/authentication-jwt/lib/index.js delete mode 100644 packages/authentication-jwt/lib/verifier.js delete mode 100644 packages/authentication-jwt/package.json delete mode 100644 packages/authentication-jwt/test/index.test.js delete mode 100644 packages/authentication-jwt/test/integration.test.js delete mode 100644 packages/authentication-jwt/test/verifier.test.js delete mode 100644 packages/authentication-local/lib/hooks/index.js create mode 100644 packages/authentication-local/lib/strategy.js delete mode 100644 packages/authentication-local/lib/utils/hash.js delete mode 100644 packages/authentication-local/lib/verifier.js create mode 100644 packages/authentication-local/test/fixture.js delete mode 100644 packages/authentication-local/test/hooks/index.test.js delete mode 100644 packages/authentication-local/test/index.test.js delete mode 100644 packages/authentication-local/test/integration.test.js create mode 100644 packages/authentication-local/test/strategy.test.js delete mode 100644 packages/authentication-local/test/verifier.test.js delete mode 100644 packages/authentication-oauth1/.npmignore delete mode 100644 packages/authentication-oauth1/CHANGELOG.md delete mode 100644 packages/authentication-oauth1/LICENSE delete mode 100644 packages/authentication-oauth1/README.md delete mode 100644 packages/authentication-oauth1/lib/express/error-handler.js delete mode 100644 packages/authentication-oauth1/lib/express/handler.js delete mode 100644 packages/authentication-oauth1/lib/index.js delete mode 100644 packages/authentication-oauth1/lib/verifier.js delete mode 100644 packages/authentication-oauth1/package.json delete mode 100644 packages/authentication-oauth1/test/express/error-handler.test.js delete mode 100644 packages/authentication-oauth1/test/express/handler.test.js delete mode 100644 packages/authentication-oauth1/test/fixtures/strategy.js delete mode 100644 packages/authentication-oauth1/test/index.test.js delete mode 100644 packages/authentication-oauth1/test/verifier.test.js delete mode 100644 packages/authentication-oauth2/.npmignore delete mode 100644 packages/authentication-oauth2/CHANGELOG.md delete mode 100644 packages/authentication-oauth2/LICENSE delete mode 100644 packages/authentication-oauth2/README.md delete mode 100644 packages/authentication-oauth2/lib/express/error-handler.js delete mode 100644 packages/authentication-oauth2/lib/express/handler.js delete mode 100644 packages/authentication-oauth2/lib/index.js delete mode 100644 packages/authentication-oauth2/lib/verifier.js delete mode 100644 packages/authentication-oauth2/package.json delete mode 100644 packages/authentication-oauth2/test/express/error-handler.test.js delete mode 100644 packages/authentication-oauth2/test/express/handler.test.js delete mode 100644 packages/authentication-oauth2/test/fixtures/strategy.js delete mode 100644 packages/authentication-oauth2/test/index.test.js delete mode 100644 packages/authentication-oauth2/test/verifier.test.js delete mode 100644 packages/authentication/lib/strategy.js diff --git a/package.json b/package.json index fd651e272c..388a942324 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "install": "lerna bootstrap", "publish": "lerna publish", "lint": "semistandard \"packages/**/lib/**/*.js\" \"packages/**/test/**/*.js\" --fix", - "test": "npm run lint && nyc lerna run test --ignore @feathersjs/authentication-*", + "test": "npm run lint && nyc lerna run test --ignore @feathersjs/authentication-client", "test:client": "grunt" }, "semistandard": { diff --git a/packages/authentication-jwt/.npmignore b/packages/authentication-jwt/.npmignore deleted file mode 100644 index 65e3ba2eda..0000000000 --- a/packages/authentication-jwt/.npmignore +++ /dev/null @@ -1 +0,0 @@ -test/ diff --git a/packages/authentication-jwt/CHANGELOG.md b/packages/authentication-jwt/CHANGELOG.md deleted file mode 100644 index 05cd7659d3..0000000000 --- a/packages/authentication-jwt/CHANGELOG.md +++ /dev/null @@ -1,233 +0,0 @@ -# Change Log - -All notable changes to this project will be documented in this file. -See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -## [2.0.10](https://github.com/feathersjs/feathers/compare/@feathersjs/authentication-jwt@2.0.9...@feathersjs/authentication-jwt@2.0.10) (2019-01-26) - -**Note:** Version bump only for package @feathersjs/authentication-jwt - - - - - -## [2.0.9](https://github.com/feathersjs/feathers/compare/@feathersjs/authentication-jwt@2.0.8...@feathersjs/authentication-jwt@2.0.9) (2019-01-02) - - -### Bug Fixes - -* Update adapter common tests ([#1135](https://github.com/feathersjs/feathers/issues/1135)) ([8166dda](https://github.com/feathersjs/feathers/commit/8166dda)) - - - - - - -## [2.0.8](https://github.com/feathersjs/feathers/compare/@feathersjs/authentication-jwt@2.0.7...@feathersjs/authentication-jwt@2.0.8) (2018-12-16) - - -### Bug Fixes - -* **chore:** Properly configure and run code linter ([#1092](https://github.com/feathersjs/feathers/issues/1092)) ([fd3fc34](https://github.com/feathersjs/feathers/commit/fd3fc34)) - - - - - - -## [2.0.7](https://github.com/feathersjs/feathers/compare/@feathersjs/authentication-jwt@2.0.6...@feathersjs/authentication-jwt@2.0.7) (2018-10-26) - - -### Bug Fixes - -* support a secretProvider ([#1063](https://github.com/feathersjs/feathers/issues/1063)) ([9da26ad](https://github.com/feathersjs/feathers/commit/9da26ad)) - - - - - - -## [2.0.6](https://github.com/feathersjs/feathers/compare/@feathersjs/authentication-jwt@2.0.5...@feathersjs/authentication-jwt@2.0.6) (2018-10-25) - - -### Bug Fixes - -* Make Mocha a proper devDependency for every repository ([#1053](https://github.com/feathersjs/feathers/issues/1053)) ([9974803](https://github.com/feathersjs/feathers/commit/9974803)) - - - - - - -## [2.0.5](https://github.com/feathersjs/feathers/compare/@feathersjs/authentication-jwt@2.0.4...@feathersjs/authentication-jwt@2.0.5) (2018-09-21) - -**Note:** Version bump only for package @feathersjs/authentication-jwt - - - - - - -## [2.0.4](https://github.com/feathersjs/feathers/compare/@feathersjs/authentication-jwt@2.0.3...@feathersjs/authentication-jwt@2.0.4) (2018-09-17) - -**Note:** Version bump only for package @feathersjs/authentication-jwt - - - - - - -## [2.0.3](https://github.com/feathersjs/feathers/compare/@feathersjs/authentication-jwt@2.0.2...@feathersjs/authentication-jwt@2.0.3) (2018-09-02) - -**Note:** Version bump only for package @feathersjs/authentication-jwt - - -## 2.0.2 - -- Migrate to Monorepo ([feathers#462](https://github.com/feathersjs/feathers/issues/462)) - -## [v2.0.1](https://github.com/feathersjs/authentication-jwt/tree/v2.0.1) (2018-05-04) -[Full Changelog](https://github.com/feathersjs/authentication-jwt/compare/v2.0.0...v2.0.1) - -**Closed issues:** - -- "No auth token" with socketio [\#60](https://github.com/feathersjs/authentication-jwt/issues/60) - -**Merged pull requests:** - -- Update sinon to the latest version 🚀 [\#59](https://github.com/feathersjs/authentication-jwt/pull/59) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) -- Update passport-jwt to the latest version 🚀 [\#58](https://github.com/feathersjs/authentication-jwt/pull/58) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) -- Update sinon-chai to the latest version 🚀 [\#57](https://github.com/feathersjs/authentication-jwt/pull/57) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) -- Fix typo on documentation link [\#56](https://github.com/feathersjs/authentication-jwt/pull/56) ([bernardobelchior](https://github.com/bernardobelchior)) - -## [v2.0.0](https://github.com/feathersjs/authentication-jwt/tree/v2.0.0) (2018-01-21) -[Full Changelog](https://github.com/feathersjs/authentication-jwt/compare/v1.0.2...v2.0.0) - -**Closed issues:** - -- Purpuse of lower casing header option [\#52](https://github.com/feathersjs/authentication-jwt/issues/52) -- Get error in custom verify [\#48](https://github.com/feathersjs/authentication-jwt/issues/48) -- Strategy loses the params on its way [\#36](https://github.com/feathersjs/authentication-jwt/issues/36) -- Strategy succeeds even if user is not found [\#27](https://github.com/feathersjs/authentication-jwt/issues/27) -- Swallowing errors when getting user from service? [\#14](https://github.com/feathersjs/authentication-jwt/issues/14) - -**Merged pull requests:** - -- Bring back Cookie token extractor [\#55](https://github.com/feathersjs/authentication-jwt/pull/55) ([daffl](https://github.com/daffl)) -- Properly pass through errors in the verifier [\#54](https://github.com/feathersjs/authentication-jwt/pull/54) ([daffl](https://github.com/daffl)) -- Update mocha to the latest version 🚀 [\#53](https://github.com/feathersjs/authentication-jwt/pull/53) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) - -## [v1.0.2](https://github.com/feathersjs/authentication-jwt/tree/v1.0.2) (2018-01-03) -[Full Changelog](https://github.com/feathersjs/authentication-jwt/compare/v1.0.1...v1.0.2) - -**Closed issues:** - -- Include data into JWT response [\#47](https://github.com/feathersjs/authentication-jwt/issues/47) - -**Merged pull requests:** - -- Update documentation to correspond with latest release [\#51](https://github.com/feathersjs/authentication-jwt/pull/51) ([daffl](https://github.com/daffl)) -- Update semistandard to the latest version 🚀 [\#50](https://github.com/feathersjs/authentication-jwt/pull/50) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) -- Update feathers-memory to the latest version 🚀 [\#49](https://github.com/feathersjs/authentication-jwt/pull/49) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) - -## [v1.0.1](https://github.com/feathersjs/authentication-jwt/tree/v1.0.1) (2017-11-16) -[Full Changelog](https://github.com/feathersjs/authentication-jwt/compare/v1.0.0...v1.0.1) - -**Closed issues:** - -- Cannot authenticate using feathers token [\#43](https://github.com/feathersjs/authentication-jwt/issues/43) - -**Merged pull requests:** - -- Add default export for better ES module \(TypeScript\) compatibility [\#46](https://github.com/feathersjs/authentication-jwt/pull/46) ([daffl](https://github.com/daffl)) -- Update @feathersjs/authentication to the latest version 🚀 [\#45](https://github.com/feathersjs/authentication-jwt/pull/45) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) - -## [v1.0.0](https://github.com/feathersjs/authentication-jwt/tree/v1.0.0) (2017-11-01) -[Full Changelog](https://github.com/feathersjs/authentication-jwt/compare/v1.0.0-pre.1...v1.0.0) - -**Merged pull requests:** - -- Update dependencies for release [\#44](https://github.com/feathersjs/authentication-jwt/pull/44) ([daffl](https://github.com/daffl)) - -## [v1.0.0-pre.1](https://github.com/feathersjs/authentication-jwt/tree/v1.0.0-pre.1) (2017-10-25) -[Full Changelog](https://github.com/feathersjs/authentication-jwt/compare/v0.3.2...v1.0.0-pre.1) - -**Closed issues:** - -- Clean generated project throws Error: You must provide a 'header' in your authentication configuration [\#35](https://github.com/feathersjs/authentication-jwt/issues/35) -- Problem with "header" configuration. Collision of config options? [\#34](https://github.com/feathersjs/authentication-jwt/issues/34) -- Please add example for Auth0 integration [\#23](https://github.com/feathersjs/authentication-jwt/issues/23) - -**Merged pull requests:** - -- Update to Feathers v3 [\#42](https://github.com/feathersjs/authentication-jwt/pull/42) ([daffl](https://github.com/daffl)) -- Rename repository and update to npm scope [\#41](https://github.com/feathersjs/authentication-jwt/pull/41) ([daffl](https://github.com/daffl)) -- Update to new plugin infrastructure [\#40](https://github.com/feathersjs/authentication-jwt/pull/40) ([daffl](https://github.com/daffl)) -- Update mocha to the latest version 🚀 [\#38](https://github.com/feathersjs/authentication-jwt/pull/38) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) -- Update sinon to the latest version 🚀 [\#37](https://github.com/feathersjs/authentication-jwt/pull/37) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) -- Update jsonwebtoken to the latest version 🚀 [\#33](https://github.com/feathersjs/authentication-jwt/pull/33) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) -- Update passport-jwt to the latest version 🚀 [\#32](https://github.com/feathersjs/authentication-jwt/pull/32) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) -- Update debug to the latest version 🚀 [\#31](https://github.com/feathersjs/authentication-jwt/pull/31) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) -- Update sinon to the latest version 🚀 [\#29](https://github.com/feathersjs/authentication-jwt/pull/29) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) -- Update chai to the latest version 🚀 [\#26](https://github.com/feathersjs/authentication-jwt/pull/26) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) - -## [v0.3.2](https://github.com/feathersjs/authentication-jwt/tree/v0.3.2) (2017-07-05) -[Full Changelog](https://github.com/feathersjs/authentication-jwt/compare/v0.3.1...v0.3.2) - -**Closed issues:** - -- Verifier is not called in a custom service route [\#24](https://github.com/feathersjs/authentication-jwt/issues/24) -- Module is using the wrong default config key [\#20](https://github.com/feathersjs/authentication-jwt/issues/20) -- Using feathers-authentication-jwt for api key authentication [\#18](https://github.com/feathersjs/authentication-jwt/issues/18) -- Possible to return user id in the payload when authenticate? [\#13](https://github.com/feathersjs/authentication-jwt/issues/13) - -**Merged pull requests:** - -- Add backwards compatible fallback for \#21 [\#25](https://github.com/feathersjs/authentication-jwt/pull/25) ([daffl](https://github.com/daffl)) -- use the correct config key name. Closes \#20 [\#21](https://github.com/feathersjs/authentication-jwt/pull/21) ([ekryski](https://github.com/ekryski)) -- Update feathers-socketio to the latest version 🚀 [\#19](https://github.com/feathersjs/authentication-jwt/pull/19) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) -- Update semistandard to the latest version 🚀 [\#17](https://github.com/feathersjs/authentication-jwt/pull/17) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) -- Update feathers-hooks to the latest version 🚀 [\#16](https://github.com/feathersjs/authentication-jwt/pull/16) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) -- Update dependencies to enable Greenkeeper 🌴 [\#15](https://github.com/feathersjs/authentication-jwt/pull/15) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) - -## [v0.3.1](https://github.com/feathersjs/authentication-jwt/tree/v0.3.1) (2016-12-29) -[Full Changelog](https://github.com/feathersjs/authentication-jwt/compare/v0.3.0...v0.3.1) - -**Closed issues:** - -- Remove `typeof 'string'` check for secret [\#9](https://github.com/feathersjs/authentication-jwt/issues/9) - -**Merged pull requests:** - -- Fix check for secret not being undefined [\#12](https://github.com/feathersjs/authentication-jwt/pull/12) ([daffl](https://github.com/daffl)) - -## [v0.3.0](https://github.com/feathersjs/authentication-jwt/tree/v0.3.0) (2016-12-14) -[Full Changelog](https://github.com/feathersjs/authentication-jwt/compare/v0.2.0...v0.3.0) - -**Closed issues:** - -- Add docs section on expected request params [\#7](https://github.com/feathersjs/authentication-jwt/issues/7) -- Add support for 'Bearer token' style header [\#5](https://github.com/feathersjs/authentication-jwt/issues/5) -- Re-using the same JWT with Primus / websockets [\#4](https://github.com/feathersjs/authentication-jwt/issues/4) - -**Merged pull requests:** - -- Document expected request data [\#8](https://github.com/feathersjs/authentication-jwt/pull/8) ([marshallswain](https://github.com/marshallswain)) -- Add support for using the Bearer scheme in the Auth header [\#6](https://github.com/feathersjs/authentication-jwt/pull/6) ([timelesshaze](https://github.com/timelesshaze)) - -## [v0.2.0](https://github.com/feathersjs/authentication-jwt/tree/v0.2.0) (2016-11-23) -[Full Changelog](https://github.com/feathersjs/authentication-jwt/compare/v0.1.0...v0.2.0) - -**Closed issues:** - -- Automatically attempt to put the entityId in the payload [\#2](https://github.com/feathersjs/authentication-jwt/issues/2) -- what's the difference between this and feathers-authentication-local? [\#1](https://github.com/feathersjs/authentication-jwt/issues/1) - -**Merged pull requests:** - -- Adding payload support and fixing config values [\#3](https://github.com/feathersjs/authentication-jwt/pull/3) ([ekryski](https://github.com/ekryski)) - -## [v0.1.0](https://github.com/feathersjs/authentication-jwt/tree/v0.1.0) (2016-11-16) - - -\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* diff --git a/packages/authentication-jwt/LICENSE b/packages/authentication-jwt/LICENSE deleted file mode 100644 index 6bfc0adefc..0000000000 --- a/packages/authentication-jwt/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2018 Feathers - -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/packages/authentication-jwt/README.md b/packages/authentication-jwt/README.md deleted file mode 100644 index d802539412..0000000000 --- a/packages/authentication-jwt/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# @feathersjs/authentication-jwt - -[![Build Status](https://travis-ci.org/feathersjs/feathers.png?branch=master)](https://travis-ci.org/feathersjs/feathers) -[![Dependency Status](https://img.shields.io/david/feathersjs/feathers.svg?style=flat-square&path=packages/authentication-jwt)](https://david-dm.org/feathersjs/feathers?path=packages/authentication-jwt) -[![Download Status](https://img.shields.io/npm/dm/@feathersjs/authentication-jwt.svg?style=flat-square)](https://www.npmjs.com/package/@feathersjs/authentication-jwt) - -> JWT authentication strategy for feathers-authentication using Passport - -## Installation - -``` -npm install @feathersjs/authentication-jwt --save -``` - -## Quick example - -```js -const feathers = require('@feathersjs/feathers'); -const authentication = require('feathers-authentication'); -const jwt = require('@feathersjs/authentication-jwt'); -const app = feathers(); - -// Setup authentication -app.configure(authentication(settings)); -app.configure(jwt()); - -// Setup a hook to only allow valid JWTs to authenticate -// and get new JWT access tokens -app.service('authentication').hooks({ - before: { - create: [ - authentication.hooks.authenticate(['jwt']) - ] - } -}); -``` - -## Documentation - -Please refer to the [@feathersjs/authentication-jwt documentation](https://docs.feathersjs.com/api/authentication/jwt.html) for more details. - - -## License - -Copyright (c) 2018 - -Licensed under the [MIT license](LICENSE). diff --git a/packages/authentication-jwt/lib/index.js b/packages/authentication-jwt/lib/index.js deleted file mode 100644 index 913f8089a4..0000000000 --- a/packages/authentication-jwt/lib/index.js +++ /dev/null @@ -1,104 +0,0 @@ -const Debug = require('debug'); -const merge = require('lodash.merge'); -const omit = require('lodash.omit'); -const pick = require('lodash.pick'); -const DefaultVerifier = require('./verifier'); -const passportJwt = require('passport-jwt'); - -const debug = Debug('@feathersjs/authentication-jwt'); -const defaults = { - name: 'jwt', - bodyKey: 'accessToken' -}; - -const KEYS = [ - 'secret', - 'header', - 'entity', - 'service', - 'passReqToCallback', - 'session', - 'jwt' -]; - -function init (options = {}) { - return function jwtAuth () { - const app = this; - const _super = app.setup; - const { ExtractJwt, Strategy } = passportJwt; - - if (!app.passport) { - throw new Error(`Can not find app.passport. Did you initialize feathers-authentication before @feathersjs/authentication-jwt?`); - } - - const authOptions = app.get('auth') || app.get('authentication') || {}; - const jwtOptions = authOptions[options.name] || {}; - // NOTE (EK): Pull from global auth config to support legacy auth for an easier transition. - const jwtSettings = merge({}, defaults, pick(authOptions, KEYS), jwtOptions, omit(options, ['Verifier'])); - - if (typeof jwtSettings.header !== 'string') { - throw new Error(`You must provide a 'header' in your authentication configuration or pass one explicitly`); - } - - const extractors = [ - ExtractJwt.fromAuthHeaderWithScheme('jwt'), - ExtractJwt.fromAuthHeaderAsBearerToken(), - ExtractJwt.fromHeader(jwtSettings.header.toLowerCase()), - ExtractJwt.fromBodyField(jwtSettings.bodyKey) - ]; - - if (authOptions.cookie && authOptions.cookie.name) { - extractors.push(function (req) { - if (req && req.cookies) { - return req.cookies[authOptions.cookie.name]; - } - - return null; - }); - } - - let Verifier = DefaultVerifier; - let strategyOptions = merge({ - secretOrKey: typeof jwtSettings.secret !== 'function' ? jwtSettings.secret : null, - secretOrKeyProvider: typeof jwtSettings.secret === 'function' ? jwtSettings.secret : null, - jwtFromRequest: ExtractJwt.fromExtractors(extractors) - }, jwtSettings.jwt, omit(jwtSettings, ['jwt', 'header', 'secret'])); - - // Normalize algorithm key - if (!strategyOptions.algorithms && strategyOptions.algorithm) { - strategyOptions.algorithms = Array.isArray(strategyOptions.algorithm) ? strategyOptions.algorithm : [strategyOptions.algorithm]; - delete strategyOptions.algorithm; - } - - // Support passing a custom verifier - if (options.Verifier) { - Verifier = options.Verifier; - } - - app.setup = function () { - let result = _super.apply(this, arguments); - let verifier = new Verifier(app, jwtSettings); - - if (!verifier.verify) { - throw new Error(`Your verifier must implement a 'verify' function. It should have the same signature as a jwt passport verify callback.`); - } - - // Register 'jwt' strategy with passport - debug('Registering jwt authentication strategy with options:', strategyOptions); - app.passport.use(jwtSettings.name, new Strategy(strategyOptions, verifier.verify.bind(verifier))); - app.passport.options(jwtSettings.name, jwtSettings); - - return result; - }; - }; -} - -module.exports = init; - -// Exposed Modules -Object.assign(module.exports, { - defaults, - default: init, - ExtractJwt: passportJwt.ExtractJwt, - Verifier: DefaultVerifier -}); diff --git a/packages/authentication-jwt/lib/verifier.js b/packages/authentication-jwt/lib/verifier.js deleted file mode 100644 index 8d62b53313..0000000000 --- a/packages/authentication-jwt/lib/verifier.js +++ /dev/null @@ -1,40 +0,0 @@ -const Debug = require('debug'); -const debug = Debug('@feathersjs/authentication-jwt:verify'); - -class JWTVerifier { - constructor (app, options = {}) { - this.app = app; - this.options = options; - this.service = typeof options.service === 'string' ? app.service(options.service) : options.service; - - if (!this.service) { - throw new Error(`options.service does not exist.\n\tMake sure you are passing a valid service path or service instance and it is initialized before @feathersjs/authentication-jwt.`); - } - - this.verify = this.verify.bind(this); - } - - verify (req, payload, done) { - debug('Received JWT payload', payload); - - const id = payload[`${this.options.entity}Id`]; - - if (id === undefined) { - debug(`JWT payload does not contain ${this.options.entity}Id`); - return done(null, {}, payload); - } - - debug(`Looking up ${this.options.entity} by id`, id); - - this.service.get(id).then(entity => { - const newPayload = { [`${this.options.entity}Id`]: id }; - return done(null, entity, newPayload); - }) - .catch(error => { - debug(`Error populating ${this.options.entity} with id ${id}`, error); - return done(error); - }); - } -} - -module.exports = JWTVerifier; diff --git a/packages/authentication-jwt/package.json b/packages/authentication-jwt/package.json deleted file mode 100644 index 022a0a9f57..0000000000 --- a/packages/authentication-jwt/package.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "name": "@feathersjs/authentication-jwt", - "description": "JWT authentication strategy for feathers-authentication using Passport", - "version": "2.0.10", - "homepage": "https://feathersjs.com", - "main": "lib/", - "keywords": [ - "feathers", - "feathers-plugin" - ], - "license": "MIT", - "repository": { - "type": "git", - "url": "git://github.com/feathersjs/feathers.git" - }, - "author": { - "name": "Feathers contributors", - "email": "hello@feathersjs.com", - "url": "https://feathersjs.com" - }, - "contributors": [], - "bugs": { - "url": "https://github.com/feathersjs/feathers/issues" - }, - "engines": { - "node": ">= 6" - }, - "scripts": { - "test": "mocha --opts ../../mocha.opts" - }, - "directories": { - "lib": "lib" - }, - "publishConfig": { - "access": "public" - }, - "dependencies": { - "@feathersjs/errors": "^3.3.6", - "debug": "^4.1.1", - "lodash.merge": "^4.6.1", - "lodash.omit": "^4.5.0", - "lodash.pick": "^4.4.0", - "passport-jwt": "^4.0.0" - }, - "devDependencies": { - "@feathersjs/authentication": "^2.1.16", - "@feathersjs/express": "^1.3.1", - "@feathersjs/feathers": "^3.3.1", - "@feathersjs/socketio": "^3.2.9", - "body-parser": "^1.18.3", - "chai": "^4.2.0", - "feathers-memory": "^3.0.2", - "jsonwebtoken": "^8.4.0", - "mocha": "^5.2.0", - "sinon": "^7.2.3", - "sinon-chai": "^3.3.0" - } -} diff --git a/packages/authentication-jwt/test/index.test.js b/packages/authentication-jwt/test/index.test.js deleted file mode 100644 index 8dd8df5d7b..0000000000 --- a/packages/authentication-jwt/test/index.test.js +++ /dev/null @@ -1,472 +0,0 @@ -/* eslint-disable no-unused-expressions */ -const JWT = require('jsonwebtoken'); -const feathers = require('@feathersjs/feathers'); -const expressify = require('@feathersjs/express'); -const authentication = require('@feathersjs/authentication'); -const memory = require('feathers-memory'); -const chai = require('chai'); -const sinon = require('sinon'); -const sinonChai = require('sinon-chai'); -const passportJWT = require('passport-jwt'); -const jwt = require('../lib'); - -const { Verifier, ExtractJwt } = jwt; -const { expect } = chai; - -chai.use(sinonChai); - -describe('@feathersjs/authentication-jwt', () => { - it('is CommonJS compatible', () => { - expect(typeof require('../lib')).to.equal('function'); - }); - - it('basic functionality', () => { - expect(typeof jwt).to.equal('function'); - expect(jwt.default).to.equal(jwt); - }); - - it('exposes the Verifier class', () => { - expect(typeof Verifier).to.equal('function'); - expect(typeof jwt.Verifier).to.equal('function'); - }); - - it('exposes the passport-jwt ExtractJwt functions', () => { - expect(typeof ExtractJwt).to.equal('object'); - expect(typeof jwt.ExtractJwt).to.equal('object'); - expect(typeof ExtractJwt.fromHeader).to.equal('function'); - expect(typeof ExtractJwt.fromBodyField).to.equal('function'); - expect(typeof ExtractJwt.fromUrlQueryParameter).to.equal('function'); - expect(typeof ExtractJwt.fromAuthHeaderWithScheme).to.equal('function'); - expect(typeof ExtractJwt.fromExtractors).to.equal('function'); - }); - - describe('secretOrKey', () => { - describe('initialization', () => { - let app; - let validToken; - let Payload = { userId: 0 }; - - beforeEach(done => { - app = expressify(feathers()); - app.use('/users', memory()); - app.configure(authentication({ - secret: 'supersecret', - cookie: { - enabled: true, - name: 'feathers-jwt' - } - })); - - app.service('users').create({ - name: 'test user' - }); - - JWT.sign(Payload, 'supersecret', app.get('authentication').jwt, (error, token) => { - if (error) { return done(error); } - validToken = token; - done(); - }); - }); - - it('throws an error if passport has not been registered', () => { - expect(() => { - expressify(feathers()).configure(jwt()); - }).to.throw(); - }); - - it('throws an error if header is not a string', () => { - expect(() => { - app.configure(jwt({ header: true })); - app.setup(); - }).to.throw(); - }); - - it('registers the jwt passport strategy', () => { - sinon.spy(app.passport, 'use'); - sinon.spy(passportJWT, 'Strategy'); - app.configure(jwt()); - app.setup(); - - expect(passportJWT.Strategy).to.have.been.calledOnce; - expect(app.passport.use).to.have.been.calledWith('jwt'); - - app.passport.use.restore(); - passportJWT.Strategy.restore(); - }); - - it('registers the strategy options', () => { - sinon.spy(app.passport, 'options'); - app.configure(jwt()); - app.setup(); - - expect(app.passport.options).to.have.been.calledOnce; - - app.passport.options.restore(); - }); - - describe('passport strategy options', () => { - let authOptions; - let args; - - beforeEach(() => { - sinon.spy(passportJWT, 'Strategy'); - app.configure(jwt({ custom: true })); - app.setup(); - authOptions = app.get('authentication'); - args = passportJWT.Strategy.getCall(0).args[0]; - }); - - afterEach(() => { - passportJWT.Strategy.restore(); - }); - - it('sets secretOrKey', () => { - expect(args.secretOrKey).to.equal('supersecret'); - }); - - it('sets jwtFromRequest', () => { - expect(args.jwtFromRequest).to.be.a('function'); - }); - - it('sets session', () => { - expect(args.session).to.equal(authOptions.session); - }); - - it('sets entity', () => { - expect(args.entity).to.equal(authOptions.entity); - }); - - it('sets service', () => { - expect(args.service).to.equal(authOptions.service); - }); - - it('sets passReqToCallback', () => { - expect(args.passReqToCallback).to.equal(authOptions.passReqToCallback); - }); - - it('sets algorithms', () => { - expect(args.algorithms).to.deep.equal([authOptions.jwt.algorithm]); - }); - - it('sets audience', () => { - expect(args.audience).to.equal(authOptions.jwt.audience); - }); - - it('sets expiresIn', () => { - expect(args.expiresIn).to.equal(authOptions.jwt.expiresIn); - }); - - it('sets issuer', () => { - expect(args.issuer).to.equal(authOptions.jwt.issuer); - }); - - it('sets subject', () => { - expect(args.subject).to.equal(authOptions.jwt.subject); - }); - - it('sets header', () => { - expect(args.header).to.deep.equal(authOptions.jwt.header); - }); - - it('supports setting custom options', () => { - expect(args.custom).to.equal(true); - }); - }); - - it('supports overriding default options', () => { - sinon.spy(passportJWT, 'Strategy'); - app.configure(jwt({ subject: 'custom' })); - app.setup(); - - expect(passportJWT.Strategy.getCall(0).args[0].subject).to.equal('custom'); - - passportJWT.Strategy.restore(); - }); - - it('pulls options from global config with custom name', () => { - sinon.spy(passportJWT, 'Strategy'); - let authOptions = app.get('authentication'); - authOptions.custom = { entity: 'device' }; - app.set('authentication', authOptions); - - app.configure(jwt({ name: 'custom' })); - app.setup(); - - expect(passportJWT.Strategy.getCall(0).args[0].entity).to.equal('device'); - expect(passportJWT.Strategy.getCall(0).args[0].bodyKey).to.equal('accessToken'); - - passportJWT.Strategy.restore(); - }); - - describe('Bearer scheme', () => { - it('authenticates using the default verifier', () => { - const req = { - query: {}, - body: {}, - headers: { - authorization: `Bearer ${validToken}` - }, - cookies: {} - }; - - app.configure(jwt()); - app.setup(); - - return app.authenticate('jwt')(req).then(result => { - expect(result.success).to.equal(true); - }); - }); - }); - - describe('Cookie', () => { - it('authenticates using a cookie if set in options', () => { - const req = { - query: {}, - body: {}, - headers: {}, - cookies: { - 'feathers-jwt': validToken - } - }; - - app.configure(jwt()); - app.setup(); - - return app.authenticate('jwt')(req).then(result => { - expect(result.success).to.equal(true); - }); - }); - }); - - describe('custom Verifier', () => { - it('throws an error if a verify function is missing', () => { - expect(() => { - class CustomVerifier { - constructor (app) { - this.app = app; - } - } - - app.configure(jwt({ Verifier: CustomVerifier })); - app.setup(); - }).to.throw(); - }); - - it('verifies through custom verify function', () => { - const req = { - query: {}, - body: {}, - headers: { - authorization: `${validToken}` - }, - cookies: {} - }; - class CustomVerifier extends Verifier { - verify (req, payload, done) { - expect(payload.userId).to.equal(Payload.userId); - done(null, payload, Payload); - } - } - - app.configure(jwt({ Verifier: CustomVerifier })); - app.setup(); - - return app.authenticate('jwt')(req).then(result => { - expect(result.data.payload.userId).to.deep.equal(Payload.userId); - }); - }); - }); - }); - }); - - describe('secretOrKeyProvider', () => { - describe('initialization', () => { - let app; - let validToken; - let Payload = { userId: 0 }; - - beforeEach(done => { - app = expressify(feathers()); - app.use('/users', memory()); - app.configure(authentication({ - secret: sinon.spy(function (request, token, done) { - done(null, 'supersecret'); - }), - cookie: { - enabled: true, - name: 'feathers-jwt' - } - })); - - app.service('users').create({ - name: 'test user' - }); - - JWT.sign(Payload, 'supersecret', app.get('authentication').jwt, (error, token) => { - if (error) { return done(error); } - validToken = token; - done(); - }); - }); - - it('throws an error if passport has not been registered', () => { - expect(() => { - expressify(feathers()).configure(jwt()); - }).to.throw(); - }); - - it('throws an error if header is not a string', () => { - expect(() => { - app.configure(jwt({ header: true })); - app.setup(); - }).to.throw(); - }); - - it('registers the jwt passport strategy', () => { - sinon.spy(app.passport, 'use'); - sinon.spy(passportJWT, 'Strategy'); - app.configure(jwt()); - app.setup(); - - expect(passportJWT.Strategy).to.have.been.calledOnce; - expect(app.passport.use).to.have.been.calledWith('jwt'); - - app.passport.use.restore(); - passportJWT.Strategy.restore(); - }); - - it('registers the strategy options', () => { - sinon.spy(app.passport, 'options'); - app.configure(jwt()); - app.setup(); - - expect(app.passport.options).to.have.been.calledOnce; - - app.passport.options.restore(); - }); - - describe('passport strategy options', () => { - let args; - - beforeEach(() => { - sinon.spy(passportJWT, 'Strategy'); - app.configure(jwt({ custom: true })); - app.setup(); - args = passportJWT.Strategy.getCall(0).args[0]; - }); - - afterEach(() => { - passportJWT.Strategy.restore(); - }); - - it('sets secretOrKeyProvider', () => { - expect(args.secretOrKeyProvider).to.be.a('function'); - }); - }); - - it('supports overriding default options', () => { - sinon.spy(passportJWT, 'Strategy'); - app.configure(jwt({ subject: 'custom' })); - app.setup(); - - expect(passportJWT.Strategy.getCall(0).args[0].subject).to.equal('custom'); - - passportJWT.Strategy.restore(); - }); - - it('pulls options from global config with custom name', () => { - sinon.spy(passportJWT, 'Strategy'); - let authOptions = app.get('authentication'); - authOptions.custom = { entity: 'device' }; - app.set('authentication', authOptions); - - app.configure(jwt({ name: 'custom' })); - app.setup(); - - expect(passportJWT.Strategy.getCall(0).args[0].entity).to.equal('device'); - expect(passportJWT.Strategy.getCall(0).args[0].bodyKey).to.equal('accessToken'); - - passportJWT.Strategy.restore(); - }); - - describe('Bearer scheme', () => { - it('authenticates using the default verifier', () => { - const req = { - query: {}, - body: {}, - headers: { - authorization: `Bearer ${validToken}` - }, - cookies: {} - }; - - app.configure(jwt()); - app.setup(); - - return app.authenticate('jwt')(req).then(result => { - expect(result.success).to.equal(true); - }); - }); - }); - - describe('Cookie', () => { - it('authenticates using a cookie if set in options', () => { - const req = { - query: {}, - body: {}, - headers: {}, - cookies: { - 'feathers-jwt': validToken - } - }; - - app.configure(jwt()); - app.setup(); - - return app.authenticate('jwt')(req).then(result => { - expect(result.success).to.equal(true); - }); - }); - }); - - describe('custom Verifier', () => { - it('throws an error if a verify function is missing', () => { - expect(() => { - class CustomVerifier { - constructor (app) { - this.app = app; - } - } - - app.configure(jwt({ Verifier: CustomVerifier })); - app.setup(); - }).to.throw(); - }); - - it('verifies through custom verify function', () => { - const req = { - query: {}, - body: {}, - headers: { - authorization: `${validToken}` - }, - cookies: {} - }; - class CustomVerifier extends Verifier { - verify (req, payload, done) { - expect(payload.userId).to.equal(Payload.userId); - done(null, payload, Payload); - } - } - - app.configure(jwt({ Verifier: CustomVerifier })); - app.setup(); - - return app.authenticate('jwt')(req).then(result => { - expect(result.data.payload.userId).to.deep.equal(Payload.userId); - }); - }); - }); - }); - }); -}); diff --git a/packages/authentication-jwt/test/integration.test.js b/packages/authentication-jwt/test/integration.test.js deleted file mode 100644 index eac4b3769a..0000000000 --- a/packages/authentication-jwt/test/integration.test.js +++ /dev/null @@ -1,88 +0,0 @@ -/* eslint-disable no-unused-expressions */ -const feathers = require('@feathersjs/feathers'); -const expressify = require('@feathersjs/express'); -const authentication = require('@feathersjs/authentication'); -const memory = require('feathers-memory'); -const { expect } = require('chai'); -const jwt = require('../lib'); - -describe('integration', () => { - it('verifies', () => { - const User = { - email: 'admin@feathersjs.com', - password: 'password' - }; - - const req = { - query: {}, - body: {}, - headers: {}, - cookies: {} - }; - - const issueJWT = () => { - return hook => { - const app = hook.app; - const id = hook.result.id; - return app.passport.createJWT({ - userId: id - }, app.get('authentication')).then(accessToken => { - hook.result.accessToken = accessToken; - return Promise.resolve(hook); - }); - }; - }; - - const app = expressify(feathers()); - - app.use('/users', memory()) - .configure(authentication({ secret: 'secret' })) - .configure(jwt()); - - app.service('users').hooks({ - after: { - create: issueJWT() - } - }); - - app.setup(); - - return app.service('users').create(User).then(user => { - req.headers = { 'authorization': user.accessToken }; - - return app.authenticate('jwt')(req).then(result => { - expect(result.success).to.equal(true); - expect(result.data.user.email).to.equal(User.email); - expect(result.data.user.password).to.not.equal(undefined); - }); - }); - }); - - it('errors when user is not found', () => { - const app = expressify(feathers()); - const req = { - query: {}, - body: {}, - headers: {}, - cookies: {} - }; - - app.use('/users', memory()) - .configure(authentication({ secret: 'secret' })) - .configure(jwt()); - - app.setup(); - - return app.passport.createJWT({ - userId: 'wrong' - }, app.get('authentication')).then(accessToken => { - req.headers = { 'authorization': accessToken }; - - return app.authenticate('jwt')(req).then(() => { - throw new Error('Should never get here'); - }).catch(error => { - expect(error.name).to.equal('NotFound'); - }); - }); - }); -}); diff --git a/packages/authentication-jwt/test/verifier.test.js b/packages/authentication-jwt/test/verifier.test.js deleted file mode 100644 index 0aebb10153..0000000000 --- a/packages/authentication-jwt/test/verifier.test.js +++ /dev/null @@ -1,142 +0,0 @@ -/* eslint-disable no-unused-expressions */ -const feathers = require('@feathersjs/feathers'); -const expressify = require('@feathersjs/express'); -const authentication = require('@feathersjs/authentication'); - -const { Verifier } = require('../lib'); - -const chai = require('chai'); -const sinon = require('sinon'); -const sinonChai = require('sinon-chai'); - -const { expect } = chai; - -chai.use(sinonChai); - -describe('Verifier', () => { - let service; - let app; - let options; - let verifier; - let user; - - beforeEach(() => { - app = expressify(feathers()); - user = { email: 'admin@feathersjs.com' }; - service = { - id: 'id', - get: sinon.stub().returns(Promise.resolve(user)) - }; - - app.use('users', service) - .configure(authentication({ secret: 'supersecret' })); - - options = app.get('authentication'); - verifier = new Verifier(app, options); - }); - - it('is CommonJS compatible', () => { - expect(typeof require('../lib/verifier')).to.equal('function'); - }); - - it('exposes the Verifier class', () => { - expect(typeof Verifier).to.equal('function'); - }); - - describe('constructor', () => { - it('retains an app reference', () => { - expect(verifier.app).to.deep.equal(app); - }); - - it('sets options', () => { - expect(verifier.options).to.deep.equal(options); - }); - - it('sets service using service path', () => { - expect(verifier.service).to.deep.equal(app.service('users')); - }); - - it('sets a passed in service instance', () => { - options.service = service; - expect(new Verifier(app, options).service).to.deep.equal(service); - }); - - describe('when service is undefined', () => { - it('throws an error', () => { - expect(() => { - new Verifier(app, {}); // eslint-disable-line - }).to.throw(); - }); - }); - }); - - describe('verify', () => { - describe('when userId is present in payload', () => { - it('calls get on the provided service', done => { - verifier.verify({}, { userId: 1 }, () => { - expect(service.get).to.have.been.calledOnce; - expect(service.get).to.have.been.calledWith(1); - done(); - }); - }); - - it('returns the payload', done => { - const payload = { userId: 1 }; - - verifier.verify({}, payload, (error, entity, p) => { - if (error) { - return done(error); - } - - expect(p).to.deep.equal(payload); - done(); - }); - }); - - it('returns the entity', done => { - verifier.verify({}, { userId: 1 }, (error, entity) => { - expect(error).to.equal(null); - expect(entity).to.deep.equal(user); - done(); - }); - }); - - describe('when service call errors', () => { - it('returns the payload', done => { - const service = { - id: 'id', - get: () => Promise.reject(new Error('User missing')) - }; - - options.service = service; - const erroringVerifier = new Verifier(app, options); - const payload = { userId: 1 }; - erroringVerifier.verify({}, payload, (error, entity, p) => { - expect(error).to.exist; - expect(error.message).to.equal('User missing'); - done(); - }); - }); - }); - }); - - describe('when userId is not present in payload', () => { - it('does not call get on the provided service', done => { - verifier.verify({}, {}, () => { - expect(service.get).to.not.have.been.called; - done(); - }); - }); - - it('returns the payload', done => { - const payload = { name: 'Eric' }; - verifier.verify({}, payload, (error, entity, p) => { - expect(error).to.equal(null); - expect(entity).to.deep.equal({}); - expect(p).to.deep.equal(payload); - done(); - }); - }); - }); - }); -}); diff --git a/packages/authentication-local/lib/hooks/hash-password.js b/packages/authentication-local/lib/hooks/hash-password.js index a5649ad5c2..b69540efb8 100644 --- a/packages/authentication-local/lib/hooks/hash-password.js +++ b/packages/authentication-local/lib/hooks/hash-password.js @@ -1,54 +1,51 @@ -const hasher = require('../utils/hash'); -const { merge, get, set, cloneDeep } = require('lodash'); -const Debug = require('debug'); +const { get, set, cloneDeep } = require('lodash'); +const { BadRequest } = require('@feathersjs/errors'); -const debug = Debug('@feathersjs/authentication-local:hooks:hash-password'); +const debug = require('debug')('@feathersjs/authentication-local/hooks/hash-password'); + +module.exports = function hashPassword (field, options = {}) { + if (!field) { + throw new Error('The hashPassword hook requires a field name option'); + } -module.exports = function hashPassword (options = {}) { return function (context) { if (context.type !== 'before') { - return Promise.reject(new Error(`The 'hashPassword' hook should only be used as a 'before' hook.`)); + return Promise.reject( + new Error(`The 'hashPassword' hook should only be used as a 'before' hook`) + ); } - const app = context.app; - const authOptions = app.get('authentication') || {}; - - options = merge({ passwordField: 'password' }, authOptions.local, options); - - debug('Running hashPassword hook with options:', options); - - const field = options.passwordField; - const hashPw = options.hash || hasher; + const { app, data, params } = context; + const password = get(data, field); - if (typeof field !== 'string') { - return Promise.reject(new Error(`You must provide a 'passwordField' in your authentication configuration or pass one explicitly`)); + if (data === undefined || password === undefined) { + debug(`hook.data or hook.data.${field} is undefined. Skipping hashPassword hook.`); + return Promise.resolve(context); } - if (typeof hashPw !== 'function') { - return Promise.reject(new Error(`'hash' must be a function that takes a password and returns Promise that resolves with a hashed password.`)); - } + const serviceName = options.authentication || app.get('defaultAuthentication'); + const authService = app.service(serviceName); + const { strategy = 'local' } = options; - if (context.data === undefined) { - debug(`hook.data is undefined. Skipping hashPassword hook.`); - return Promise.resolve(context); + if (!authService || typeof authService.getStrategies !== 'function') { + return Promise.reject( + new BadRequest(`Could not find '${serviceName}' service to hash password`) + ); } - const dataIsArray = Array.isArray(context.data); - const data = dataIsArray ? context.data : [ context.data ]; + const [ localStrategy ] = authService.getStrategies(strategy); - return Promise.all(data.map(item => { - const password = get(item, field); - if (password) { - return hashPw(password).then(hashedPassword => - set(cloneDeep(item), field, hashedPassword) - ); - } + if (!localStrategy || typeof localStrategy.hashPassword !== 'function') { + return Promise.reject( + new BadRequest(`Could not find '${strategy}' strategy to hash password`) + ); + } - return item; - })).then(results => { - context.data = dataIsArray ? results : results[0]; + return localStrategy.hashPassword(password, params) + .then(hashedPassword => { + context.data = set(cloneDeep(data), field, hashedPassword); - return context; - }); + return context; + }); }; }; diff --git a/packages/authentication-local/lib/hooks/index.js b/packages/authentication-local/lib/hooks/index.js deleted file mode 100644 index 6059b9388c..0000000000 --- a/packages/authentication-local/lib/hooks/index.js +++ /dev/null @@ -1,7 +0,0 @@ -const hashPassword = require('./hash-password'); -const protect = require('./protect'); - -module.exports = { - hashPassword, - protect -}; diff --git a/packages/authentication-local/lib/index.js b/packages/authentication-local/lib/index.js index c5965c0c2f..2ab9462a0d 100644 --- a/packages/authentication-local/lib/index.js +++ b/packages/authentication-local/lib/index.js @@ -1,69 +1,7 @@ -const Debug = require('debug'); -const { merge, omit, pick } = require('lodash'); -const hooks = require('./hooks'); -const DefaultVerifier = require('./verifier'); +const LocalStrategy = require('./strategy'); +const hashPassword = require('./hooks/hash-password'); +const protect = require('./hooks/protect'); -const passportLocal = require('passport-local'); - -const debug = Debug('@feathersjs/authentication-local'); -const defaults = { - name: 'local', - usernameField: 'email', - passwordField: 'password' -}; - -const KEYS = [ - 'entity', - 'service', - 'passReqToCallback', - 'session' -]; - -function init (options = {}) { - return function localAuth () { - const app = this; - const _super = app.setup; - - if (!app.passport) { - throw new Error(`Can not find app.passport. Did you initialize feathers-authentication before @feathersjs/authentication-local?`); - } - - let name = options.name || defaults.name; - let authOptions = app.get('authentication') || {}; - let localOptions = authOptions[name] || {}; - - // NOTE (EK): Pull from global auth config to support legacy auth for an easier transition. - const localSettings = merge({}, defaults, pick(authOptions, KEYS), localOptions, omit(options, ['Verifier'])); - let Verifier = DefaultVerifier; - - if (options.Verifier) { - Verifier = options.Verifier; - } - - app.setup = function () { - let result = _super.apply(this, arguments); - let verifier = new Verifier(app, localSettings); - - if (!verifier.verify) { - throw new Error(`Your verifier must implement a 'verify' function. It should have the same signature as a local passport verify callback.`); - } - - // Register 'local' strategy with passport - debug('Registering local authentication strategy with options:', localSettings); - app.passport.use(localSettings.name, new passportLocal.Strategy(localSettings, verifier.verify.bind(verifier))); - app.passport.options(localSettings.name, localSettings); - - return result; - }; - }; -} - -module.exports = init; - -// Exposed Modules -Object.assign(module.exports, { - default: init, - defaults, - hooks, - Verifier: DefaultVerifier -}); +exports.LocalStrategy = LocalStrategy; +exports.protect = protect; +exports.hashPassword = hashPassword; diff --git a/packages/authentication-local/lib/strategy.js b/packages/authentication-local/lib/strategy.js new file mode 100644 index 0000000000..628c31aa73 --- /dev/null +++ b/packages/authentication-local/lib/strategy.js @@ -0,0 +1,123 @@ +const bcrypt = require('bcryptjs'); +const { NotAuthenticated } = require('@feathersjs/errors'); +const { get, omit } = require('lodash'); + +const debug = require('debug')('@feathersjs/authentication-local/strategy'); + +module.exports = class LocalStrategy { + setAuthentication (auth) { + this.authentication = auth; + } + + setApplication (app) { + this.app = app; + } + + setName (name) { + this.name = name; + } + + verifyConfiguration () { + const config = this.configuration; + + [ 'usernameField', 'passwordField' ].forEach(prop => { + if (typeof config[prop] !== 'string') { + throw new Error(`'${this.name}' authentication strategy requires a '${prop}' setting`); + } + }); + } + + get configuration () { + const authConfig = this.authentication.configuration; + const config = authConfig[this.name] || {}; + + return Object.assign({}, { + hashSize: 10, + service: authConfig.service, + entity: authConfig.entity, + errorMessage: 'Invalid login', + entityPasswordField: config.passwordField, + entityUsernameField: config.usernameField + }, config); + } + + getEntityQuery (query) { + return Promise.resolve(Object.assign({ + $limit: 1 + }, query)); + } + + findEntity (username, params) { + const { entityUsernameField, service, errorMessage } = this.configuration; + + return this.getEntityQuery({ + [entityUsernameField]: username + }, params).then(query => { + const findParams = Object.assign({}, params, { query }); + const entityService = this.app.service(service); + + debug('Finding entity with query', params.query); + + return entityService.find(findParams); + }).then(result => { + const list = Array.isArray(result) ? result : result.data; + + if (!Array.isArray(list) || list.length === 0) { + debug(`No entity found`); + + return Promise.reject(new NotAuthenticated(errorMessage)); + } + + const [ entity ] = list; + + return entity; + }); + } + + comparePassword (entity, password) { + const { entityPasswordField, errorMessage } = this.configuration; + // find password in entity, this allows for dot notation + const hash = get(entity, entityPasswordField); + + if (!hash) { + debug(`Record is missing the '${entityPasswordField}' password field`); + + return Promise.reject(new NotAuthenticated(errorMessage)); + } + + debug('Verifying password'); + + return bcrypt.compare(password, hash).then(result => { + if (result) { + return entity; + } + + throw new NotAuthenticated(errorMessage); + }); + } + + hashPassword (password) { + return bcrypt.hash(password, this.configuration.hashSize); + } + + authenticate (data, params) { + const { passwordField, usernameField, entity, errorMessage } = this.configuration; + const username = data[usernameField]; + const password = data[passwordField]; + + if (data.strategy && data.strategy !== this.name) { + return Promise.reject(new NotAuthenticated(errorMessage)); + } + + return this.findEntity(username, omit(params, 'provider')) + .then(entity => this.comparePassword(entity, password)) + .then(entity => params.provider + ? this.findEntity(username, params) : entity + ).then(authEntity => { + return { + authentication: { strategy: this.name }, + [entity]: authEntity + }; + }); + } +}; diff --git a/packages/authentication-local/lib/utils/hash.js b/packages/authentication-local/lib/utils/hash.js deleted file mode 100644 index 90b3b6391a..0000000000 --- a/packages/authentication-local/lib/utils/hash.js +++ /dev/null @@ -1,27 +0,0 @@ -const bcrypt = require('bcryptjs'); - -const BCRYPT_WORK_FACTOR_BASE = 12; -const BCRYPT_DATE_BASE = 1483228800000; -const BCRYPT_WORK_INCREASE_INTERVAL = 47300000000; - -module.exports = function hasher (password) { - return new Promise((resolve, reject) => { - let BCRYPT_CURRENT_DATE = new Date().getTime(); - let BCRYPT_WORK_INCREASE = Math.max(0, Math.floor((BCRYPT_CURRENT_DATE - BCRYPT_DATE_BASE) / BCRYPT_WORK_INCREASE_INTERVAL)); - let BCRYPT_WORK_FACTOR = Math.min(19, BCRYPT_WORK_FACTOR_BASE + BCRYPT_WORK_INCREASE); - - bcrypt.genSalt(BCRYPT_WORK_FACTOR, function (error, salt) { - if (error) { - return reject(error); - } - - bcrypt.hash(password, salt, function (error, hashedPassword) { - if (error) { - return reject(error); - } - - resolve(hashedPassword); - }); - }); - }); -}; diff --git a/packages/authentication-local/lib/verifier.js b/packages/authentication-local/lib/verifier.js deleted file mode 100644 index 188ee4baed..0000000000 --- a/packages/authentication-local/lib/verifier.js +++ /dev/null @@ -1,103 +0,0 @@ -const Debug = require('debug'); -const bcrypt = require('bcryptjs'); -const { get, omit } = require('lodash'); - -const debug = Debug('@feathersjs/authentication-local:verify'); - -class LocalVerifier { - constructor (app, options = {}) { - this.app = app; - this.options = options; - this.service = typeof options.service === 'string' ? app.service(options.service) : options.service; - - if (!this.service) { - throw new Error(`options.service does not exist.\n\tMake sure you are passing a valid service path or service instance and it is initialized before @feathersjs/authentication-local.`); - } - - this._comparePassword = this._comparePassword.bind(this); - this._normalizeResult = this._normalizeResult.bind(this); - this.verify = this.verify.bind(this); - } - - _comparePassword (entity, password) { - // select entity password field - take entityPasswordField over passwordField - const passwordField = this.options.entityPasswordField || this.options.passwordField; - - // find password in entity, this allows for dot notation - const hash = get(entity, passwordField); - - if (!hash) { - return Promise.reject(new Error(`'${this.options.entity}' record in the database is missing a '${passwordField}'`)); - } - - debug('Verifying password'); - - return new Promise((resolve, reject) => { - bcrypt.compare(password, hash, function (error, result) { - // Handle 500 server error. - if (error) { - return reject(error); - } - - if (!result) { - debug('Password incorrect'); - return reject(false); // eslint-disable-line - } - - debug('Password correct'); - return resolve(entity); - }); - }); - } - - _normalizeResult (results) { - // Paginated services return the array of results in the data attribute. - let entities = results.data ? results.data : results; - let entity = entities[0]; - - // Handle bad username. - if (!entity) { - return Promise.reject(false); // eslint-disable-line - } - - debug(`${this.options.entity} found`); - return Promise.resolve(entity); - } - - verify (req, username, password, done) { - debug('Checking credentials', username, password); - - const id = this.service.id; - const usernameField = this.options.entityUsernameField || this.options.usernameField; - const params = Object.assign({ - 'query': { - [usernameField]: username, - '$limit': 1 - } - }, omit(req.params, 'query', 'provider', 'headers', 'session', 'cookies')); - - if (id === null || id === undefined) { - debug('failed: the service.id was not set'); - return done(new Error('the `id` property must be set on the entity service for authentication')); - } - - // Look up the entity - this.service.find(params) - .then(response => { - const results = response.data || response; - if (!results.length) { - debug(`a record with ${usernameField} of '${username}' did not exist`); - } - return this._normalizeResult(response); - }) - .then(entity => this._comparePassword(entity, password)) - .then(entity => { - const id = entity[this.service.id]; - const payload = { [`${this.options.entity}Id`]: id }; - done(null, entity, payload); - }) - .catch(error => error ? done(error) : done(null, error, { message: 'Invalid login' })); - } -} - -module.exports = LocalVerifier; diff --git a/packages/authentication-local/package.json b/packages/authentication-local/package.json index 33ad1d936e..af4729a1db 100644 --- a/packages/authentication-local/package.json +++ b/packages/authentication-local/package.json @@ -38,20 +38,11 @@ "@feathersjs/errors": "^3.3.6", "bcryptjs": "^2.4.3", "debug": "^4.1.1", - "lodash": "^4.17.11", - "passport-local": "^1.0.0" + "lodash": "^4.17.11" }, "devDependencies": { "@feathersjs/authentication": "^2.1.16", - "@feathersjs/authentication-jwt": "^2.0.10", - "@feathersjs/express": "^1.3.1", "@feathersjs/feathers": "^3.3.1", - "@feathersjs/socketio": "^3.2.9", - "body-parser": "^1.18.3", - "chai": "^4.2.0", - "feathers-memory": "^3.0.2", - "mocha": "^5.2.0", - "sinon": "^7.2.3", - "sinon-chai": "^3.3.0" + "mocha": "^5.2.0" } } diff --git a/packages/authentication-local/test/fixture.js b/packages/authentication-local/test/fixture.js new file mode 100644 index 0000000000..8c8964e44e --- /dev/null +++ b/packages/authentication-local/test/fixture.js @@ -0,0 +1,38 @@ +const feathers = require('@feathersjs/feathers'); +const memory = require('feathers-memory'); +const { AuthenticationService, JWTStrategy } = require('@feathersjs/authentication'); + +const { LocalStrategy, hashPassword, protect } = require('../lib'); + +module.exports = (app = feathers()) => { + const authentication = new AuthenticationService(app); + + app.set('authentication', { + secret: 'supersecret', + strategies: [ 'local', 'jwt' ], + local: { + usernameField: 'email', + passwordField: 'password' + } + }); + + authentication.register('jwt', new JWTStrategy()); + authentication.register('local', new LocalStrategy()); + + app.use('/authentication', authentication); + app.use('/users', memory({ + paginate: { + default: 10, + max: 20 + } + })); + + app.service('users').hooks({ + before: { + create: hashPassword('password') + }, + after: protect('password') + }); + + return app; +}; diff --git a/packages/authentication-local/test/hooks/hash-password.test.js b/packages/authentication-local/test/hooks/hash-password.test.js index d354a4e6ee..db57af297f 100644 --- a/packages/authentication-local/test/hooks/hash-password.test.js +++ b/packages/authentication-local/test/hooks/hash-password.test.js @@ -1,178 +1,85 @@ -/* eslint-disable no-unused-expressions */ -const chai = require('chai'); -const sinon = require('sinon'); -const sinonChai = require('sinon-chai'); +const assert = require('assert'); -const { hashPassword } = require('../../lib/hooks'); -const { expect } = chai; +const getApp = require('../fixture'); +const { hashPassword } = require('../../lib'); -chai.use(sinonChai); - -describe('hooks:hashPassword', () => { - let hook; +describe('@feathersjs/authentication-local/hooks/hash-password', () => { + let app; beforeEach(() => { - hook = { - type: 'before', - data: { password: 'secret' }, - params: {}, - app: { - get: () => { - return { - local: { - passwordField: 'password' - } - }; - } - } - }; + app = getApp(); }); - describe('when not called as a before hook', () => { - it('returns an error', () => { - hook.type = 'after'; - return hashPassword()(hook).catch(error => { - expect(error).to.not.equal(undefined); - }); - }); + it('throws error when no field provided', () => { + try { + hashPassword(); + assert.fail('Should never get here'); + } catch (error) { + assert.strictEqual(error.message, 'The hashPassword hook requires a field name option'); + } }); - describe('when data does not exist', () => { - it('does not do anything', () => { - delete hook.data; - return hashPassword()(hook).then(returnedHook => { - expect(returnedHook).to.deep.equal(hook); - }); - }); - }); - - describe('when data does not have a prototype', () => { - it('does not do anything', () => { - hook.data = Object.create(null); - return hashPassword()(hook).then(returnedHook => { - expect(returnedHook).to.deep.equal(hook); - }); - }); - }); + it('errors when authentication service does not exist', () => { + delete app.services.authentication; - describe('when password does not exist', () => { - it('does not do anything', () => { - delete hook.data.password; - return hashPassword()(hook).then(returnedHook => { - expect(returnedHook).to.deep.equal(hook); - }); + return app.service('users').create({ + email: 'dave@hashpassword.com', + password: 'supersecret' + }).then(() => assert.fail('Should never get here')).catch(error => { + assert.strictEqual(error.message, + `Could not find 'authentication' service to hash password` + ); }); }); - describe('when password exists', () => { - it('hashes with options from global auth config', () => { - return hashPassword()(hook).then(hook => { - expect(hook.data.password).to.not.equal(undefined); - expect(hook.data.password).to.not.equal('secret'); - }); - }); - - it('does not modify the original object', () => { - const data = { password: 'secret' }; - - hook.data = data; - - return hashPassword()(hook).then(hook => { - expect(hook.data.password).to.not.equal(undefined); - expect(hook.data.password).to.not.equal('secret'); - expect(data.password).to.equal('secret'); - }); - }); - - it('hashes with custom options', () => { - hook.data.pass = 'secret'; + it('errors when authentication strategy does not exist', () => { + delete app.services.authentication.strategies.local; - return hashPassword({ passwordField: 'pass' })(hook).then(hook => { - expect(hook.data.pass).to.not.equal(undefined); - expect(hook.data.pass).to.not.equal('secret'); - }); - }); - - it('hashes with nested password field custom option', () => { - hook.data = { - nested: { - pass: 'secret' - } - }; - - return hashPassword({ passwordField: 'nested.pass' })(hook).then(hook => { - expect(hook.data.nested.pass).to.not.equal(undefined); - expect(hook.data.nested.pass).to.not.equal('secret'); - }); - }); - - it('calls custom hash function', () => { - const fn = sinon.stub().returns(Promise.resolve()); - return hashPassword({ hash: fn })(hook).then(() => { - expect(fn).to.have.been.calledOnce; - expect(fn).to.have.been.calledWith('secret'); - }); - }); - - it('returns an error when custom hash is not a function', () => { - return hashPassword({ hash: true })(hook).catch(error => { - expect(error).to.not.equal(undefined); - }); + return app.service('users').create({ + email: 'dave@hashpassword.com', + password: 'supersecret' + }).then(() => assert.fail('Should never get here')).catch(error => { + assert.strictEqual(error.message, + `Could not find 'local' strategy to hash password` + ); }); }); - describe('when password exists in bulk', () => { - beforeEach(() => { - hook.data = [ - { password: 'secret' }, - { password: 'secret' } - ]; - }); + it('errors when authentication strategy does not exist', () => { + const users = app.service('users'); - it('hashes with options from global auth config', () => { - return hashPassword()(hook).then(hook => { - hook.data.map(item => { - expect(item.password).to.not.equal(undefined); - expect(item.password).to.not.equal('secret'); - }); - }); + users.hooks({ + after: { + create: hashPassword('password') + } }); - it('does not remove things if there is no password', () => { - hook.data = [ - { id: 0, password: 'secret' }, - { id: 1 } - ]; - - return hashPassword()(hook).then(hook => { - const { data } = hook; - - expect(data.length).to.equal(2); - expect(data[0].password).to.not.equal('secret'); - expect(data[1]).to.exist; - }); + return users.create({ + email: 'dave@hashpassword.com', + password: 'supersecret' + }).then(() => assert.fail('Should never get here')).catch(error => { + assert.strictEqual(error.message, + `The 'hashPassword' hook should only be used as a 'before' hook` + ); }); + }); - it('hashes with custom options', () => { - hook.data = [ - { pass: 'secret' }, - { pass: 'secret' } - ]; + it('hashes password on field', () => { + const password = 'supersecret'; - return hashPassword({ passwordField: 'pass' })(hook).then(hook => { - hook.data.map(item => { - expect(item.pass).to.not.equal(undefined); - expect(item.pass).to.not.equal('secret'); - }); - }); + return app.service('users').create({ + email: 'dave@hashpassword.com', + password + }).then(user => { + assert.notStrictEqual(user.password, password); }); + }); - it('calls custom hash function', () => { - const fn = sinon.stub().returns(Promise.resolve()); - return hashPassword({ hash: fn })(hook).then(() => { - expect(fn).to.have.been.calledTwice; - expect(fn).to.have.been.calledWith('secret'); - }); + it('does nothing when field is not present', () => { + return app.service('users').create({ + email: 'dave@hashpassword.com' + }).then(user => { + assert.strictEqual(user.password, undefined); }); }); }); diff --git a/packages/authentication-local/test/hooks/index.test.js b/packages/authentication-local/test/hooks/index.test.js deleted file mode 100644 index 8893f0b4c7..0000000000 --- a/packages/authentication-local/test/hooks/index.test.js +++ /dev/null @@ -1,16 +0,0 @@ -const { expect } = require('chai'); -const hooks = require('../../lib/hooks'); - -describe('hooks', () => { - it('is CommonJS compatible', () => { - expect(typeof require('../../lib/hooks')).to.equal('object'); - }); - - it('is ES6 compatible', () => { - expect(typeof hooks).to.equal('object'); - }); - - it('exposes hashPassword hook', () => { - expect(typeof hooks.hashPassword).to.equal('function'); - }); -}); diff --git a/packages/authentication-local/test/hooks/protect.test.js b/packages/authentication-local/test/hooks/protect.test.js index e51d963d0f..8defea60df 100644 --- a/packages/authentication-local/test/hooks/protect.test.js +++ b/packages/authentication-local/test/hooks/protect.test.js @@ -1,8 +1,5 @@ -/* eslint-disable no-unused-expressions */ -const chai = require('chai'); - -const { protect } = require('../../lib/hooks'); -const { expect } = chai; +const assert = require('assert'); +const { protect } = require('../../lib'); function testOmit (title, property) { describe(title, () => { @@ -18,7 +15,7 @@ function testOmit (title, property) { }; const result = fn(context); - expect(result).to.deep.equal({ + assert.deepStrictEqual(result, { [property]: data, dispatch: { email: 'test@user.com' } }); @@ -37,7 +34,7 @@ function testOmit (title, property) { }; const result = hook(context); - expect(result).to.deep.equal({ + assert.deepStrictEqual(result, { [property]: data, dispatch: { user: { email: 'test@user.com' } } }); @@ -54,7 +51,7 @@ function testOmit (title, property) { }; const result = fn(context); - expect(result).to.deep.equal({ + assert.deepStrictEqual(result, { [property]: data, dispatch: { email: 'test@user.com', data: 'yes' } }); @@ -76,7 +73,7 @@ function testOmit (title, property) { }; const result = fn(context); - expect(result).to.deep.equal({ + assert.deepStrictEqual(result, { [property]: data, dispatch: { email: 'test@user.com' } }); @@ -95,7 +92,7 @@ function testOmit (title, property) { }; const result = fn(context); - expect(result).to.deep.equal({ + assert.deepStrictEqual(result, { [property]: data, dispatch: [ { email: 'test1@user.com' }, @@ -121,7 +118,7 @@ function testOmit (title, property) { }; const result = fn(context); - expect(result).to.deep.equal({ + assert.deepStrictEqual(result, { method: 'find', [property]: data, dispatch: { @@ -149,7 +146,7 @@ function testOmit (title, property) { }; const result = fn(context); - expect(result).to.deep.equal({ + assert.deepStrictEqual(result, { [property]: data, params, result: [ @@ -165,12 +162,12 @@ function testOmit (title, property) { }); } -describe('hooks:protect', () => { +describe('@feathersjs/authentication-local/hooks/protect', () => { it('does nothing when called with no result', () => { const fn = protect(); const original = {}; - expect(fn(original)).to.deep.equal(original); + assert.deepStrictEqual(fn(original), original); }); testOmit('with hook.result', 'result'); diff --git a/packages/authentication-local/test/index.test.js b/packages/authentication-local/test/index.test.js deleted file mode 100644 index 20d28fdbeb..0000000000 --- a/packages/authentication-local/test/index.test.js +++ /dev/null @@ -1,204 +0,0 @@ -/* eslint-disable no-unused-expressions */ -const feathers = require('@feathersjs/feathers'); -const expressify = require('@feathersjs/express'); -const authentication = require('@feathersjs/authentication'); -const memory = require('feathers-memory'); -const chai = require('chai'); -const sinon = require('sinon'); -const sinonChai = require('sinon-chai'); -const passportLocal = require('passport-local'); -const local = require('../lib'); - -const { Verifier } = local; -const { expect } = chai; - -chai.use(sinonChai); - -describe('@feathersjs/authentication-local', () => { - it('is CommonJS compatible', () => { - expect(typeof require('../lib')).to.equal('function'); - }); - - it('basic functionality', () => { - expect(typeof local).to.equal('function'); - }); - - it('exposes default', () => { - expect(local.default).to.equal(local); - }); - - it('exposes hooks', () => { - expect(typeof local.hooks).to.equal('object'); - }); - - it('exposes the Verifier class', () => { - expect(typeof Verifier).to.equal('function'); - expect(typeof local.Verifier).to.equal('function'); - }); - - describe('initialization', () => { - let app; - - beforeEach(() => { - app = expressify(feathers()); - app.use('/users', memory()); - app.configure(authentication({ secret: 'supersecret' })); - }); - - it('throws an error if passport has not been registered', () => { - expect(() => { - expressify(feathers()).configure(local()); - }).to.throw(); - }); - - it('registers the local passport strategy', () => { - sinon.spy(app.passport, 'use'); - sinon.spy(passportLocal, 'Strategy'); - app.configure(local()); - app.setup(); - - expect(passportLocal.Strategy).to.have.been.calledOnce; - expect(app.passport.use).to.have.been.calledWith('local'); - - app.passport.use.restore(); - passportLocal.Strategy.restore(); - }); - - it('registers the strategy options', () => { - sinon.spy(app.passport, 'options'); - app.configure(local()); - app.setup(); - - expect(app.passport.options).to.have.been.calledOnce; - - app.passport.options.restore(); - }); - - describe('passport strategy options', () => { - let authOptions; - let args; - - beforeEach(() => { - sinon.spy(passportLocal, 'Strategy'); - app.configure(local({ custom: true })); - app.setup(); - authOptions = app.get('authentication'); - args = passportLocal.Strategy.getCall(0).args[0]; - }); - - afterEach(() => { - passportLocal.Strategy.restore(); - }); - - it('sets usernameField', () => { - expect(args.usernameField).to.equal('email'); - }); - - it('sets passwordField', () => { - expect(args.passwordField).to.equal('password'); - }); - - it('sets entity', () => { - expect(args.entity).to.equal(authOptions.entity); - }); - - it('sets service', () => { - expect(args.service).to.equal(authOptions.service); - }); - - it('sets session', () => { - expect(args.session).to.equal(authOptions.session); - }); - - it('sets passReqToCallback', () => { - expect(args.passReqToCallback).to.equal(authOptions.passReqToCallback); - }); - - it('supports setting custom options', () => { - expect(args.custom).to.equal(true); - }); - }); - - it('supports overriding default options', () => { - sinon.spy(passportLocal, 'Strategy'); - app.configure(local({ usernameField: 'username' })); - app.setup(); - - expect(passportLocal.Strategy.getCall(0).args[0].usernameField).to.equal('username'); - - passportLocal.Strategy.restore(); - }); - - it('pulls options from global config', () => { - sinon.spy(passportLocal, 'Strategy'); - let authOptions = app.get('authentication'); - authOptions.local = { usernameField: 'username' }; - app.set('authentication', authOptions); - - app.configure(local()); - app.setup(); - - expect(passportLocal.Strategy.getCall(0).args[0].usernameField).to.equal('username'); - expect(passportLocal.Strategy.getCall(0).args[0].passwordField).to.equal('password'); - - passportLocal.Strategy.restore(); - }); - - it('pulls options from global config with custom name', () => { - sinon.spy(passportLocal, 'Strategy'); - let authOptions = app.get('authentication'); - authOptions.custom = { usernameField: 'username' }; - app.set('authentication', authOptions); - - app.configure(local({ name: 'custom' })); - app.setup(); - - expect(passportLocal.Strategy.getCall(0).args[0].usernameField).to.equal('username'); - expect(passportLocal.Strategy.getCall(0).args[0].passwordField).to.equal('password'); - - passportLocal.Strategy.restore(); - }); - - describe('custom Verifier', () => { - it('throws an error if a verify function is missing', () => { - expect(() => { - class CustomVerifier { - constructor (app) { - this.app = app; - } - } - app.configure(local({ Verifier: CustomVerifier })); - app.setup(); - }).to.throw(); - }); - - it('verifies through custom verify function', () => { - const User = { - email: 'admin@feathersjs.com', - password: 'password' - }; - - const req = { - query: {}, - body: Object.assign({}, User), - headers: {}, - cookies: {} - }; - class CustomVerifier extends Verifier { - verify (req, username, password, done) { - expect(username).to.equal(User.email); - expect(password).to.equal(User.password); - done(null, User); - } - } - - app.configure(local({ Verifier: CustomVerifier })); - app.setup(); - - return app.authenticate('local')(req).then(result => { - expect(result.data.user).to.deep.equal(User); - }); - }); - }); - }); -}); diff --git a/packages/authentication-local/test/integration.test.js b/packages/authentication-local/test/integration.test.js deleted file mode 100644 index a5bf64ee53..0000000000 --- a/packages/authentication-local/test/integration.test.js +++ /dev/null @@ -1,62 +0,0 @@ -/* eslint-disable no-unused-expressions */ -const feathers = require('@feathersjs/feathers'); -const expressify = require('@feathersjs/express'); -const authentication = require('@feathersjs/authentication'); -const memory = require('feathers-memory'); -const local = require('../lib'); - -const { expect } = require('chai'); - -describe('integration', () => { - it('verifies', () => { - const User = { - email: 'admin@feathersjs.com', - password: 'password' - }; - - const req = { - query: {}, - body: Object.assign({}, User), - headers: {}, - cookies: {}, - params: { - query: {}, - provider: 'socketio', - headers: {}, - session: {}, - cookies: {}, - data: 'Hello, world' - } - }; - - const app = expressify(feathers()); - let paramsReceived = false; - let dataReceived; - - app.configure(authentication({ secret: 'secret' })) - .configure(local()) - .use('/users', memory()); - - app.service('users').hooks({ - before: { - find: (hook) => { - paramsReceived = Object.keys(hook.params); - dataReceived = hook.params.data; - }, - create: local.hooks.hashPassword({ passwordField: 'password' }) - } - }); - - app.setup(); - - return app.service('users').create(User).then(() => { - return app.authenticate('local')(req).then(result => { - expect(result.success).to.equal(true); - expect(result.data.user.email).to.equal(User.email); - expect(result.data.user.password).to.not.equal(undefined); - expect(paramsReceived).to.have.members(['data', 'query']); - expect(dataReceived).to.equal('Hello, world'); - }); - }); - }); -}); diff --git a/packages/authentication-local/test/strategy.test.js b/packages/authentication-local/test/strategy.test.js new file mode 100644 index 0000000000..8e1cd7ad08 --- /dev/null +++ b/packages/authentication-local/test/strategy.test.js @@ -0,0 +1,133 @@ +const assert = require('assert'); +const { LocalStrategy } = require('../lib'); +const getApp = require('./fixture'); + +describe('@feathersjs/authentication-local/strategy', () => { + const password = 'localsecret'; + const email = 'localtester@feathersjs.com'; + + let app, user; + + beforeEach(() => { + app = getApp(); + + return app.service('users') + .create({ email, password }) + .then(createdUser => { + user = createdUser; + }); + }); + + it('throw error when configuration is not set', () => { + const auth = app.service('authentication'); + + try { + auth.register('something', new LocalStrategy()); + assert.fail('Should never get here'); + } catch (error) { + assert.strictEqual(error.message, + `'something' authentication strategy requires a 'usernameField' setting` + ); + } + }); + + it('fails when entity not found', () => { + const authService = app.service('authentication'); + return authService.create({ + strategy: 'local', + email: 'not in database', + password + }).then(() => { + assert.fail('Should never get here'); + }).catch(error => { + assert.strictEqual(error.name, 'NotAuthenticated'); + assert.strictEqual(error.message, 'Invalid login'); + }); + }); + + it('strategy fails when strategy is different', () => { + const [ local ] = app.service('authentication').getStrategies('local'); + return local.authenticate({ + strategy: 'not-me', + password: 'dummy', + email + }).then(() => { + assert.fail('Should never get here'); + }).catch(error => { + assert.strictEqual(error.name, 'NotAuthenticated'); + assert.strictEqual(error.message, 'Invalid login'); + }); + }); + + it('fails when password is wrong', () => { + const authService = app.service('authentication'); + return authService.create({ + strategy: 'local', + email, + password: 'dummy' + }).then(() => { + assert.fail('Should never get here'); + }).catch(error => { + assert.strictEqual(error.name, 'NotAuthenticated'); + assert.strictEqual(error.message, 'Invalid login'); + }); + }); + + it('fails when password field is not available', () => { + const userEmail = 'someuser@localtest.com'; + const authService = app.service('authentication'); + + return app.service('users').create({ + email: userEmail + }).then(() => authService.create({ + strategy: 'local', + password: 'dummy', + email: userEmail + })).then(() => { + assert.fail('Should never get here'); + }).catch(error => { + assert.strictEqual(error.name, 'NotAuthenticated'); + assert.strictEqual(error.message, 'Invalid login'); + }); + }); + + it('authenticates an existing user', () => { + const authService = app.service('authentication'); + return authService.create({ + strategy: 'local', + email, + password + }).then(authResult => { + const { accessToken } = authResult; + + assert.ok(accessToken); + assert.strictEqual(authResult.user.email, email); + + return authService.verifyJWT(accessToken); + }).then(decoded => { + assert.strictEqual(decoded.sub, `${user.id}`); + }); + }); + + it('returns safe result when params.provider is set, works without pagination', () => { + const authService = app.service('authentication'); + return authService.create({ + strategy: 'local', + email, + password + }, { + provider: 'rest', + paginate: false + }).then(authResult => { + const { accessToken } = authResult; + + assert.ok(accessToken); + assert.strictEqual(authResult.user.email, email); + assert.strictEqual(authResult.user.passsword, undefined); + + return authService.verifyJWT(accessToken); + }).then(decoded => { + assert.strictEqual(decoded.sub, `${user.id}`); + }); + }); +}); diff --git a/packages/authentication-local/test/verifier.test.js b/packages/authentication-local/test/verifier.test.js deleted file mode 100644 index 07004edf6c..0000000000 --- a/packages/authentication-local/test/verifier.test.js +++ /dev/null @@ -1,305 +0,0 @@ -/* eslint-disable no-unused-expressions */ -const feathers = require('@feathersjs/feathers'); -const authentication = require('@feathersjs/authentication'); -const expressify = require('@feathersjs/express'); -const hasher = require('../lib/utils/hash'); -const { Verifier, defaults } = require('../lib'); - -const chai = require('chai'); -const sinon = require('sinon'); -const sinonChai = require('sinon-chai'); - -const { expect } = require('chai'); - -chai.use(sinonChai); - -describe('Verifier', () => { - let service; - let app; - let options; - let verifier; - let user; - - beforeEach(() => { - app = expressify(feathers()); - - return hasher('admin').then(password => { - user = { - email: 'admin@feathersjs.com', - password - }; - - service = { - id: 'id', - find () {} - }; - - sinon.stub(service, 'find').callsFake(function (params) { - return new Promise((resolve, reject) => { - const { email } = params && params.query; - if (email === 'nonexistinguser@gmail.com') { - return resolve([]); - } - return resolve([user]); - }); - }); - - app.use('users', service) - .configure(authentication({ secret: 'supersecret' })); - - options = Object.assign({}, defaults, app.get('authentication')); - - verifier = new Verifier(app, options); - }); - }); - - it('is CommonJS compatible', () => { - expect(typeof require('../lib/verifier')).to.equal('function'); - }); - - it('exposes the Verifier class', () => { - expect(typeof Verifier).to.equal('function'); - }); - - describe('constructor', () => { - it('retains an app reference', () => { - expect(verifier.app).to.deep.equal(app); - }); - - it('sets options', () => { - expect(verifier.options).to.deep.equal(options); - }); - - it('sets service using service path', () => { - expect(verifier.service).to.deep.equal(app.service('users')); - }); - - it('sets a passed in service instance', () => { - options.service = service; - expect(new Verifier(app, options).service).to.deep.equal(service); - }); - - describe('when service is undefined', () => { - it('throws an error', () => { - expect(() => { - new Verifier(app, {}); // eslint-disable-line - }).to.throw(); - }); - }); - }); - - describe('_comparePassword', () => { - describe('when entity is missing password field', () => { - it('returns an error', () => { - return verifier._comparePassword({}).catch(error => { - expect(error).to.not.equal(undefined); - }); - }); - }); - - describe('password comparison fails', () => { - it('rejects with false', () => { - return verifier._comparePassword(user, 'invalid').catch(error => { - expect(error).to.equal(false); - }); - }); - }); - - describe('password comparison succeeds', () => { - it('returns the entity', () => { - return verifier._comparePassword(user, 'admin').then(result => { - expect(result).to.deep.equal(user); - }); - }); - - it('allows dot notation for password field', () => { - user.password = { - value: user.password - }; - - verifier.options.passwordField = 'password.value'; - - return verifier._comparePassword(user, 'admin').then(result => { - expect(result).to.deep.equal(user); - }); - }); - - it('prefers entityPasswordField over passwordField', () => { - user.password = { - value: user.password - }; - - verifier.options.passwordField = 'password'; - verifier.options.entityPasswordField = 'password.value'; - - return verifier._comparePassword(user, 'admin').then(result => { - expect(result).to.deep.equal(user); - }); - }); - }); - }); - - describe('_normalizeResult', () => { - describe('when has results', () => { - it('returns entity when paginated', () => { - return verifier._normalizeResult({ data: [user] }).then(result => { - expect(result).to.deep.equal(user); - }); - }); - - it('returns entity when not paginated', () => { - return verifier._normalizeResult([user]).then(result => { - expect(result).to.deep.equal(user); - }); - }); - }); - - describe('when no results', () => { - it('rejects with false when paginated', () => { - return verifier._normalizeResult({ data: [] }).catch(error => { - expect(error).to.equal(false); - }); - }); - - it('rejects with false when not paginated', () => { - return verifier._normalizeResult([]).catch(error => { - expect(error).to.equal(false); - }); - }); - }); - }); - - describe('verify', () => { - it('calls find on the provided service', done => { - verifier.verify({}, user.email, 'admin', () => { - const query = { email: user.email, $limit: 1 }; - expect(service.find).to.have.been.calledOnce; - expect(service.find).to.have.been.calledWith({ query }); - done(); - }); - }); - - it('allows overriding of usernameField', done => { - verifier.options.usernameField = 'username'; - - user.username = 'username'; - - verifier.verify({}, 'username', 'admin', (error, entity) => { - expect(error).to.equal(null); - expect(entity).to.deep.equal(user); - done(); - }); - }); - - it('prefers entityUsernameField over usernameField', done => { - verifier.options.usernameField = 'username'; - verifier.options.entityUsernameField = 'users.username'; - - user.username = 'invalid'; - - user.users = { - username: 'valid' - }; - - verifier.verify({}, 'valid', 'admin', (error, entity) => { - expect(error).to.equal(null); - expect(entity).to.deep.equal(user); - done(); - }); - }); - - it('calls _normalizeResult', done => { - sinon.spy(verifier, '_normalizeResult'); - verifier.verify({}, user.email, 'admin', () => { - expect(verifier._normalizeResult).to.have.been.calledOnce; - verifier._normalizeResult.restore(); - done(); - }); - }); - - it('produces an error message when the user did not exist', done => { - verifier.verify({}, 'nonexistinguser@gmail.com', 'admin', (err, user, info) => { - expect(err).to.not.be.undefined; - expect(info.message).to.equal('Invalid login'); - done(); - }); - }); - - it('calls _comparePassword', done => { - sinon.spy(verifier, '_comparePassword'); - verifier.verify({}, user.email, 'admin', () => { - expect(verifier._comparePassword).to.have.been.calledOnce; - verifier._comparePassword.restore(); - done(); - }); - }); - - it('returns the entity', done => { - verifier.verify({}, user.email, 'admin', (error, entity) => { - expect(error).to.equal(null); - expect(entity).to.deep.equal(user); - done(); - }); - }); - - it('handles false rejections in promise chain', (done) => { - verifier._normalizeResult = () => Promise.reject(false); // eslint-disable-line - verifier.verify({}, user.email, 'admin', (error, entity) => { - expect(error).to.equal(null); - expect(entity).to.equal(false); - done(); - }); - }); - - it('returns errors', (done) => { - const authError = new Error('An error'); - verifier._normalizeResult = () => Promise.reject(authError); - verifier.verify({}, user.email, 'admin', (error, entity) => { - expect(error).to.equal(authError); - expect(entity).to.equal(undefined); - done(); - }); - }); - }); -}); - -describe('Verifier without service.id', function () { - let service; - let app; - let options; - let verifier; - let user; - - beforeEach(() => { - app = expressify(feathers()); - - return hasher('admin').then(password => { - user = { - email: 'admin@feathersjs.com', - password - }; - - // testing a missing service.id - service = { - find () { - return Promise.resolve([]); - } - }; - - app.use('users', service) - .configure(authentication({ secret: 'supersecret' })); - - options = Object.assign({}, defaults, app.get('authentication')); - - verifier = new Verifier(app, options); - }); - }); - - it('throws an error when service.id is not set', done => { - verifier.verify({}, user.email, 'admin', (error, entity) => { - expect(error.message.includes('the `id` property must be set')).to.equal(true); - expect(entity).to.equal(undefined); - done(); - }); - }); -}); diff --git a/packages/authentication-oauth1/.npmignore b/packages/authentication-oauth1/.npmignore deleted file mode 100644 index 65e3ba2eda..0000000000 --- a/packages/authentication-oauth1/.npmignore +++ /dev/null @@ -1 +0,0 @@ -test/ diff --git a/packages/authentication-oauth1/CHANGELOG.md b/packages/authentication-oauth1/CHANGELOG.md deleted file mode 100644 index e95ad655b6..0000000000 --- a/packages/authentication-oauth1/CHANGELOG.md +++ /dev/null @@ -1,208 +0,0 @@ -# Change Log - -All notable changes to this project will be documented in this file. -See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -## [1.1.1](https://github.com/feathersjs/feathers/compare/@feathersjs/authentication-oauth1@1.1.0...@feathersjs/authentication-oauth1@1.1.1) (2019-01-26) - - -### Bug Fixes - -* **authentication:** Fall back when req.app is not the application when emitting events ([#1185](https://github.com/feathersjs/feathers/issues/1185)) ([6a534f0](https://github.com/feathersjs/feathers/commit/6a534f0)) - - - - - -# [1.1.0](https://github.com/feathersjs/feathers/compare/@feathersjs/authentication-oauth1@1.0.10...@feathersjs/authentication-oauth1@1.1.0) (2019-01-06) - - -### Features - -* Make custom query for oAuth authentication ([#1124](https://github.com/feathersjs/feathers/issues/1124)) ([5d43e3c](https://github.com/feathersjs/feathers/commit/5d43e3c)) - - - - - -## [1.0.10](https://github.com/feathersjs/feathers/compare/@feathersjs/authentication-oauth1@1.0.9...@feathersjs/authentication-oauth1@1.0.10) (2019-01-02) - - -### Bug Fixes - -* Update adapter common tests ([#1135](https://github.com/feathersjs/feathers/issues/1135)) ([8166dda](https://github.com/feathersjs/feathers/commit/8166dda)) - - - - - - -## [1.0.9](https://github.com/feathersjs/feathers/compare/@feathersjs/authentication-oauth1@1.0.8...@feathersjs/authentication-oauth1@1.0.9) (2018-12-16) - -**Note:** Version bump only for package @feathersjs/authentication-oauth1 - - - - - - -## [1.0.8](https://github.com/feathersjs/feathers/compare/@feathersjs/authentication-oauth1@1.0.7...@feathersjs/authentication-oauth1@1.0.8) (2018-10-25) - - -### Bug Fixes - -* Make Mocha a proper devDependency for every repository ([#1053](https://github.com/feathersjs/feathers/issues/1053)) ([9974803](https://github.com/feathersjs/feathers/commit/9974803)) - - - - - - -## [1.0.7](https://github.com/feathersjs/feathers/compare/@feathersjs/authentication-oauth1@1.0.6...@feathersjs/authentication-oauth1@1.0.7) (2018-09-21) - -**Note:** Version bump only for package @feathersjs/authentication-oauth1 - - - - - - -## [1.0.6](https://github.com/feathersjs/feathers/compare/@feathersjs/authentication-oauth1@1.0.5...@feathersjs/authentication-oauth1@1.0.6) (2018-09-17) - -**Note:** Version bump only for package @feathersjs/authentication-oauth1 - - - - - - -## [1.0.5](https://github.com/feathersjs/feathers/compare/@feathersjs/authentication-oauth1@1.0.4...@feathersjs/authentication-oauth1@1.0.5) (2018-09-02) - -**Note:** Version bump only for package @feathersjs/authentication-oauth1 - - -## 1.0.4 - -- Migrate to Monorepo ([feathers#462](https://github.com/feathersjs/feathers/issues/462)) - -## [v1.0.3](https://github.com/feathersjs/authentication-oauth1/tree/v1.0.3) (2018-01-03) -[Full Changelog](https://github.com/feathersjs/authentication-oauth1/compare/v1.0.2...v1.0.3) - -**Merged pull requests:** - -- Update documentation to correspond with latest release [\#40](https://github.com/feathersjs/authentication-oauth1/pull/40) ([daffl](https://github.com/daffl)) -- Update semistandard to the latest version 🚀 [\#39](https://github.com/feathersjs/authentication-oauth1/pull/39) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) -- Update feathers-memory to the latest version 🚀 [\#38](https://github.com/feathersjs/authentication-oauth1/pull/38) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) - -## [v1.0.2](https://github.com/feathersjs/authentication-oauth1/tree/v1.0.2) (2017-11-16) -[Full Changelog](https://github.com/feathersjs/authentication-oauth1/compare/v1.0.1...v1.0.2) - -**Closed issues:** - -- No error handler? [\#34](https://github.com/feathersjs/authentication-oauth1/issues/34) - -**Merged pull requests:** - -- Add default ES module default exports to make TypeScript integration … [\#37](https://github.com/feathersjs/authentication-oauth1/pull/37) ([daffl](https://github.com/daffl)) -- Update @feathersjs/authentication to the latest version 🚀 [\#36](https://github.com/feathersjs/authentication-oauth1/pull/36) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) - -## [v1.0.1](https://github.com/feathersjs/authentication-oauth1/tree/v1.0.1) (2017-11-06) -[Full Changelog](https://github.com/feathersjs/authentication-oauth1/compare/v1.0.0...v1.0.1) - -**Merged pull requests:** - -- Add a missing errorr handler [\#35](https://github.com/feathersjs/authentication-oauth1/pull/35) ([Avnerus](https://github.com/Avnerus)) - -## [v1.0.0](https://github.com/feathersjs/authentication-oauth1/tree/v1.0.0) (2017-11-01) -[Full Changelog](https://github.com/feathersjs/authentication-oauth1/compare/v1.0.0-pre.1...v1.0.0) - -**Merged pull requests:** - -- Update dependencies for release [\#33](https://github.com/feathersjs/authentication-oauth1/pull/33) ([daffl](https://github.com/daffl)) - -## [v1.0.0-pre.1](https://github.com/feathersjs/authentication-oauth1/tree/v1.0.0-pre.1) (2017-10-25) -[Full Changelog](https://github.com/feathersjs/authentication-oauth1/compare/v0.2.6...v1.0.0-pre.1) - -**Merged pull requests:** - -- Update to Feathers v3 [\#32](https://github.com/feathersjs/authentication-oauth1/pull/32) ([daffl](https://github.com/daffl)) -- Rename repository and use npm scope [\#31](https://github.com/feathersjs/authentication-oauth1/pull/31) ([daffl](https://github.com/daffl)) -- Update to new plugin infrastructure [\#30](https://github.com/feathersjs/authentication-oauth1/pull/30) ([daffl](https://github.com/daffl)) - -## [v0.2.6](https://github.com/feathersjs/authentication-oauth1/tree/v0.2.6) (2017-10-15) -[Full Changelog](https://github.com/feathersjs/authentication-oauth1/compare/v0.2.5...v0.2.6) - -**Closed issues:** - -- An in-range update of feathers is breaking the build 🚨 [\#23](https://github.com/feathersjs/authentication-oauth1/issues/23) -- Log a warning if this.service.id is undefined or null [\#16](https://github.com/feathersjs/authentication-oauth1/issues/16) - -**Merged pull requests:** - -- Erroring if service.id is undefined. Closes \#16 [\#29](https://github.com/feathersjs/authentication-oauth1/pull/29) ([ekryski](https://github.com/ekryski)) -- Fix old property name fallback for backwards compatibility [\#28](https://github.com/feathersjs/authentication-oauth1/pull/28) ([ekryski](https://github.com/ekryski)) -- changing to patch instead of update when updating an existing user [\#27](https://github.com/feathersjs/authentication-oauth1/pull/27) ([ekryski](https://github.com/ekryski)) -- Update mocha to the latest version 🚀 [\#26](https://github.com/feathersjs/authentication-oauth1/pull/26) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) -- Update sinon to the latest version 🚀 [\#25](https://github.com/feathersjs/authentication-oauth1/pull/25) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) -- Add babel-polyfill and package-lock.json [\#24](https://github.com/feathersjs/authentication-oauth1/pull/24) ([daffl](https://github.com/daffl)) -- Update debug to the latest version 🚀 [\#22](https://github.com/feathersjs/authentication-oauth1/pull/22) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) -- Update sinon to the latest version 🚀 [\#21](https://github.com/feathersjs/authentication-oauth1/pull/21) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) - -## [v0.2.5](https://github.com/feathersjs/authentication-oauth1/tree/v0.2.5) (2017-06-21) -[Full Changelog](https://github.com/feathersjs/authentication-oauth1/compare/v0.2.4...v0.2.5) - -**Closed issues:** - -- Module is using the wrong default config key [\#17](https://github.com/feathersjs/authentication-oauth1/issues/17) -- When authenticating with stored localStorage jwt, payload doesn't have userId. [\#15](https://github.com/feathersjs/authentication-oauth1/issues/15) -- Support oauth1 endpoint within sub-app or reverse proxy [\#9](https://github.com/feathersjs/authentication-oauth1/issues/9) -- failureRedirect is not followed if it fails during OAuth dance [\#8](https://github.com/feathersjs/authentication-oauth1/issues/8) - -**Merged pull requests:** - -- chore\(package\): update chai to version 4.0.2 [\#20](https://github.com/feathersjs/authentication-oauth1/pull/20) ([daffl](https://github.com/daffl)) -- using correct auth key. Closes 17 [\#18](https://github.com/feathersjs/authentication-oauth1/pull/18) ([ekryski](https://github.com/ekryski)) -- Update semistandard to the latest version 🚀 [\#14](https://github.com/feathersjs/authentication-oauth1/pull/14) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) -- Update feathers-hooks to the latest version 🚀 [\#13](https://github.com/feathersjs/authentication-oauth1/pull/13) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) -- Update dependencies to enable Greenkeeper 🌴 [\#12](https://github.com/feathersjs/authentication-oauth1/pull/12) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) - -## [v0.2.4](https://github.com/feathersjs/authentication-oauth1/tree/v0.2.4) (2017-03-31) -[Full Changelog](https://github.com/feathersjs/authentication-oauth1/compare/v0.2.3...v0.2.4) - -**Closed issues:** - -- userId is missing in jwt payload [\#5](https://github.com/feathersjs/authentication-oauth1/issues/5) -- accessToken is not returned when successRedirect is set [\#4](https://github.com/feathersjs/authentication-oauth1/issues/4) - -**Merged pull requests:** - -- Issue \#9: Support oauth1 endpoint within sub-app or reverse proxy [\#11](https://github.com/feathersjs/authentication-oauth1/pull/11) ([buske](https://github.com/buske)) -- Issue \#8: Follow failureRedirect on oauth1 authentication failure [\#10](https://github.com/feathersjs/authentication-oauth1/pull/10) ([buske](https://github.com/buske)) - -## [v0.2.3](https://github.com/feathersjs/authentication-oauth1/tree/v0.2.3) (2016-12-14) -[Full Changelog](https://github.com/feathersjs/authentication-oauth1/compare/v0.2.2...v0.2.3) - -## [v0.2.2](https://github.com/feathersjs/authentication-oauth1/tree/v0.2.2) (2016-12-14) -[Full Changelog](https://github.com/feathersjs/authentication-oauth1/compare/v0.2.1...v0.2.2) - -**Merged pull requests:** - -- setting \_\_oauth value and fixing redirects [\#3](https://github.com/feathersjs/authentication-oauth1/pull/3) ([ekryski](https://github.com/ekryski)) - -## [v0.2.1](https://github.com/feathersjs/authentication-oauth1/tree/v0.2.1) (2016-12-14) -[Full Changelog](https://github.com/feathersjs/authentication-oauth1/compare/v0.2.0...v0.2.1) - -## [v0.2.0](https://github.com/feathersjs/authentication-oauth1/tree/v0.2.0) (2016-11-23) -[Full Changelog](https://github.com/feathersjs/authentication-oauth1/compare/v0.1.1...v0.2.0) - -**Merged pull requests:** - -- adding compatibility with payload generation [\#2](https://github.com/feathersjs/authentication-oauth1/pull/2) ([ekryski](https://github.com/ekryski)) - -## [v0.1.1](https://github.com/feathersjs/authentication-oauth1/tree/v0.1.1) (2016-11-20) -[Full Changelog](https://github.com/feathersjs/authentication-oauth1/compare/v0.1.0...v0.1.1) - -## [v0.1.0](https://github.com/feathersjs/authentication-oauth1/tree/v0.1.0) (2016-11-19) - - -\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* diff --git a/packages/authentication-oauth1/LICENSE b/packages/authentication-oauth1/LICENSE deleted file mode 100644 index 6bfc0adefc..0000000000 --- a/packages/authentication-oauth1/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2018 Feathers - -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/packages/authentication-oauth1/README.md b/packages/authentication-oauth1/README.md deleted file mode 100644 index bdc03d54e9..0000000000 --- a/packages/authentication-oauth1/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# @feathersjs/authentication-oauth1 - -[![Build Status](https://travis-ci.org/feathersjs/feathers.png?branch=master)](https://travis-ci.org/feathersjs/feathers) -[![Dependency Status](https://img.shields.io/david/feathersjs/feathers.svg?style=flat-square&path=packages/authentication-oauth1)](https://david-dm.org/feathersjs/feathers?path=packages/authentication-oauth1) -[![Download Status](https://img.shields.io/npm/dm/@feathersjs/authentication-oauth1.svg?style=flat-square)](https://www.npmjs.com/package/@feathersjs/authentication-oauth1) - -> A Feathers OAuth1 authentication strategy - -## Installation - -``` -npm install @feathersjs/authentication-oauth1 --save -``` - -## Quick example - -```js -const feathers = require('@feathersjs/feathers'); -const authentication = require('feathers-authentication'); -const jwt = require('feathers-authentication-jwt'); -const oauth1 = require('@feathersjs/authentication-oauth1'); -const session = require('express-session'); -const TwitterStrategy = require('passport-twitter').Strategy; -const app = feathers(); - -// Setup in memory session -app.use(session({ - secret: 'super secret', - resave: true, - saveUninitialized: true -})); - -// Setup authentication -app.configure(authentication(settings)); -app.configure(jwt()); -app.configure(oauth1({ - name: 'twitter', - Strategy: TwitterStrategy, - consumerKey: '', - consumerSecret: '' -})); - -// Setup a hook to only allow valid JWTs to authenticate -// and get new JWT access tokens -app.service('authentication').hooks({ - before: { - create: [ - authentication.hooks.authenticate(['jwt']) - ] - } -}); -``` - -## Documentation - -Please refer to the [@feathersjs/authentication-oauth1 documentation](http://docs.feathersjs.com/) for more details. - -## License - -Copyright (c) 2018 - -Licensed under the [MIT license](LICENSE). diff --git a/packages/authentication-oauth1/lib/express/error-handler.js b/packages/authentication-oauth1/lib/express/error-handler.js deleted file mode 100644 index cdb6211806..0000000000 --- a/packages/authentication-oauth1/lib/express/error-handler.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = function OAuthErrorHandler (options = {}) { - return function (err, req, res, next) { - // Set __redirect so that later middleware (e.g., auth.express.failureRedirect) can redirect accordingly - if (options.failureRedirect) { - res.hook = { data: {} }; - Object.defineProperty(res.hook.data, '__redirect', { value: { status: 302, url: options.failureRedirect } }); - } - - next(err); - }; -}; diff --git a/packages/authentication-oauth1/lib/express/handler.js b/packages/authentication-oauth1/lib/express/handler.js deleted file mode 100644 index 75cb83e39c..0000000000 --- a/packages/authentication-oauth1/lib/express/handler.js +++ /dev/null @@ -1,34 +0,0 @@ -const Debug = require('debug'); - -const debug = Debug('@feathersjs/authentication-oauth1:handler'); - -module.exports = function OAuthHandler (options = {}) { - return function (req, res, next) { - const app = req.app; - const authSettings = app.get('auth') || app.get('authentication') || {}; - const entity = req[options.entity]; - const payload = req.payload; - const params = { - authenticated: true, - [options.entity]: entity, - payload - }; - const data = { - [options.entity]: entity, - payload - }; - - debug(`Executing '${options.name}' OAuth Callback`); - debug(`Calling create on '${authSettings.path}' service with`, entity); - app.service(authSettings.path).create(data, params).then(result => { - res.data = result; - - if (options.successRedirect) { - res.hook = { data: {} }; - Object.defineProperty(res.hook.data, '__redirect', { value: { status: 302, url: options.successRedirect } }); - } - - next(); - }).catch(next); - }; -}; diff --git a/packages/authentication-oauth1/lib/index.js b/packages/authentication-oauth1/lib/index.js deleted file mode 100644 index 1651671ea3..0000000000 --- a/packages/authentication-oauth1/lib/index.js +++ /dev/null @@ -1,113 +0,0 @@ -const Debug = require('debug'); -const auth = require('@feathersjs/authentication'); -const rest = require('@feathersjs/express/rest'); - -const { makeUrl, _ } = require('@feathersjs/commons'); -const { omit, pick } = _; - -const merge = require('lodash.merge'); -const defaultHandler = require('./express/handler'); -const defaultErrorHandler = require('./express/error-handler'); -const DefaultVerifier = require('./verifier'); - -const debug = Debug('@feathersjs/authentication-oauth1'); - -const INCLUDE_KEYS = [ - 'entity', - 'service', - 'passReqToCallback' -]; - -const EXCLUDE_KEYS = ['Verifier', 'Strategy', 'formatter']; - -function init (options = {}) { - return function oauth1Auth () { - const app = this; - const _super = app.setup; - - if (!app.passport) { - throw new Error(`Can not find app.passport. Did you initialize feathers-authentication before @feathersjs/authentication-oauth1?`); - } - - let { name, Strategy } = options; - - if (!name) { - throw new Error(`You must provide a strategy 'name'.`); - } - - if (!Strategy) { - throw new Error(`You must provide a passport 'Strategy' instance.`); - } - - const authSettings = app.get('auth') || app.get('authentication') || {}; - - // Attempt to pull options from the global auth config - // for this provider. - const providerSettings = authSettings[name] || {}; - const oauth1Settings = merge({ - idField: `${name}Id`, - path: `/auth/${name}`, - session: true, - __oauth: true - }, pick(authSettings, ...INCLUDE_KEYS), providerSettings, omit(options, ...EXCLUDE_KEYS)); - - // Set callback defaults based on provided path - oauth1Settings.callbackPath = oauth1Settings.callbackPath || `${oauth1Settings.path}/callback`; - oauth1Settings.callbackURL = oauth1Settings.callbackURL || makeUrl(oauth1Settings.callbackPath, app); - - if (!oauth1Settings.consumerKey) { - throw new Error(`You must provide a 'consumerKey' in your authentication configuration or pass one explicitly`); - } - - if (!oauth1Settings.consumerSecret) { - throw new Error(`You must provide a 'consumerSecret' in your authentication configuration or pass one explicitly`); - } - - const Verifier = options.Verifier || DefaultVerifier; - const formatter = options.formatter || rest.formatter; - const handler = options.handler || defaultHandler(oauth1Settings); - const errorHandler = defaultErrorHandler(oauth1Settings); - - // register OAuth middleware - debug(`Registering '${name}' Express OAuth middleware`); - app.get(oauth1Settings.path, auth.express.authenticate(name, oauth1Settings)); - app.get( - oauth1Settings.callbackPath, - // NOTE (EK): We register failure redirect here so that we can - // retain the natural express middleware redirect ability like - // you would have with vanilla passport. - auth.express.authenticate(name, oauth1Settings), - handler, - errorHandler, - auth.express.emitEvents(authSettings, app), - auth.express.setCookie(authSettings), - auth.express.successRedirect(), - auth.express.failureRedirect(authSettings), - formatter - ); - - app.setup = function () { - let result = _super.apply(this, arguments); - let verifier = new Verifier(app, oauth1Settings); - - if (!verifier.verify) { - throw new Error(`Your verifier must implement a 'verify' function. It should have the same signature as a oauth1 passport verify callback.`); - } - - // Register 'oauth1' strategy with passport - debug('Registering oauth1 authentication strategy with options:', oauth1Settings); - app.passport.use(name, new Strategy(oauth1Settings, verifier.verify.bind(verifier))); - app.passport.options(name, oauth1Settings); - - return result; - }; - }; -} - -module.exports = init; - -// Exposed Modules -Object.assign(module.exports, { - default: init, - Verifier: DefaultVerifier -}); diff --git a/packages/authentication-oauth1/lib/verifier.js b/packages/authentication-oauth1/lib/verifier.js deleted file mode 100644 index df9c490b88..0000000000 --- a/packages/authentication-oauth1/lib/verifier.js +++ /dev/null @@ -1,120 +0,0 @@ -const Debug = require('debug'); - -const debug = Debug('@feathersjs/authentication-oauth1:verify'); - -class OAuth1Vierifier { - constructor (app, options = {}) { - this.app = app; - this.options = options; - this.service = typeof options.service === 'string' ? app.service(options.service) : options.service; - - options.makeQuery = options.makeQuery || function (profile, options) { - return { [options.idField]: profile.id }; // facebookId: profile.id - }; - - if (!this.service) { - throw new Error(`options.service does not exist.\n\tMake sure you are passing a valid service path or service instance and it is initialized before @feathersjs/authentication-oauth1.`); - } - - this._createEntity = this._createEntity.bind(this); - this._updateEntity = this._updateEntity.bind(this); - this._normalizeResult = this._normalizeResult.bind(this); - this.verify = this.verify.bind(this); - } - - _normalizeResult (results) { - // Paginated services return the array of results in the data attribute. - let entities = results.data ? results.data : results; - let entity = entities[0]; - - // Handle entity not found. - if (!entity) { - return Promise.resolve(null); - } - - // Handle updating mongoose models - if (typeof entity.toObject === 'function') { - entity = entity.toObject(); - } else if (typeof entity.toJSON === 'function') { - // Handle updating Sequelize models - entity = entity.toJSON(); - } - - debug(`${this.options.entity} found`); - return Promise.resolve(entity); - } - - _updateEntity (entity, data) { - const options = this.options; - const name = options.name; - const id = entity[this.service.id]; - debug(`Updating ${options.entity}: ${id}`); - - const newData = { - [options.idField]: data.profile.id, - [name]: data - }; - - return this.service.patch(id, newData, { oauth: { provider: name } }); - } - - _createEntity (data) { - const options = this.options; - const name = options.name; - const entity = { - [options.idField]: data.profile.id, - [name]: data - }; - - const id = entity[options.idField]; - debug(`Creating new ${options.entity} with ${options.idField}: ${id}`); - - return this.service.create(entity, { oauth: { provider: name } }); - } - - verify (req, accessToken, refreshToken, profile, done) { - debug('Checking credentials'); - const options = this.options; - const query = Object.assign({}, options.makeQuery(profile, options), { $limit: 1 }); - const data = { profile, accessToken, refreshToken }; - let existing; - - if (this.service.id === null || this.service.id === undefined) { - debug('failed: the service.id was not set'); - return done(new Error('the `id` property must be set on the entity service for authentication')); - } - - // Check request object for an existing entity - if (req && req[options.entity]) { - existing = req[options.entity]; - } - - // Check the request that came from a hook for an existing entity - if (!existing && req && req.params && req.params[options.entity]) { - existing = req.params[options.entity]; - } - - // If there is already an entity on the request object (ie. they are - // already authenticated) attach the profile to the existing entity - // because they are likely "linking" social accounts/profiles. - if (existing) { - return this._updateEntity(existing, data) - .then(entity => done(null, entity)) - .catch(error => error ? done(error) : done(null, error)); - } - - // Find or create the user since they could have signed up via facebook. - this.service - .find({ query }) - .then(this._normalizeResult) - .then(entity => entity ? this._updateEntity(entity, data) : this._createEntity(data)) - .then(entity => { - const id = entity[this.service.id]; - const payload = { [`${this.options.entity}Id`]: id }; - done(null, entity, payload); - }) - .catch(error => error ? done(error) : done(null, error)); - } -} - -module.exports = OAuth1Vierifier; diff --git a/packages/authentication-oauth1/package.json b/packages/authentication-oauth1/package.json deleted file mode 100644 index c6935d4c80..0000000000 --- a/packages/authentication-oauth1/package.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "name": "@feathersjs/authentication-oauth1", - "description": "A Feathers OAuth1 authentication strategy", - "version": "1.1.1", - "homepage": "https://feathersjs.com", - "main": "lib/", - "keywords": [ - "feathers", - "feathers-plugin" - ], - "license": "MIT", - "repository": { - "type": "git", - "url": "git://github.com/feathersjs/feathers.git" - }, - "author": { - "name": "Feathers contributors", - "email": "hello@feathersjs.com", - "url": "https://feathersjs.com" - }, - "contributors": [], - "bugs": { - "url": "https://github.com/feathersjs/feathers/issues" - }, - "engines": { - "node": ">= 6" - }, - "scripts": { - "test": "mocha --opts ../../mocha.opts" - }, - "directories": { - "lib": "lib" - }, - "publishConfig": { - "access": "public" - }, - "dependencies": { - "@feathersjs/commons": "^4.0.0", - "@feathersjs/errors": "^3.3.6", - "debug": "^4.1.1", - "lodash.merge": "^4.6.1" - }, - "devDependencies": { - "@feathersjs/authentication": "^2.1.16", - "@feathersjs/express": "^1.3.1", - "@feathersjs/feathers": "^3.3.1", - "body-parser": "^1.18.3", - "chai": "^4.2.0", - "express-session": "^1.15.6", - "feathers-memory": "^3.0.2", - "mocha": "^5.2.0", - "passport-strategy": "^1.0.0", - "passport-twitter": "^1.0.4", - "sinon": "^7.2.3", - "sinon-chai": "^3.3.0" - } -} diff --git a/packages/authentication-oauth1/test/express/error-handler.test.js b/packages/authentication-oauth1/test/express/error-handler.test.js deleted file mode 100644 index c7a9fe35ac..0000000000 --- a/packages/authentication-oauth1/test/express/error-handler.test.js +++ /dev/null @@ -1,54 +0,0 @@ -/* eslint-disable no-unused-expressions */ -const { expect } = require('chai'); - -const errorHandler = require('../../lib/express/error-handler'); - -describe('express:error-handler', () => { - let req; - let res; - let error; - let options; - - beforeEach(() => { - req = {}; - options = {}; - res = { - hook: { - data: { - __redirect: { - url: '/app' - } - } - } - }; - error = new Error('Authentication Error'); - }); - - describe('when failureRedirect is set', () => { - it('sets the redirect object on the response', done => { - options.failureRedirect = '/login'; - errorHandler(options)(error, req, res, () => { - expect(res.hook.data.__redirect).to.deep.equal({ status: 302, url: options.failureRedirect }); - done(); - }); - }); - - it('calls next with error', done => { - delete res.hook; - errorHandler(options)(error, req, res, e => { - expect(e).to.equal(error); - done(); - }); - }); - }); - - describe('when failureRedirect is not set', done => { - it('calls next with error', done => { - delete res.hook; - errorHandler(options)(error, req, res, e => { - expect(e).to.equal(error); - done(); - }); - }); - }); -}); diff --git a/packages/authentication-oauth1/test/express/handler.test.js b/packages/authentication-oauth1/test/express/handler.test.js deleted file mode 100644 index 665a25b563..0000000000 --- a/packages/authentication-oauth1/test/express/handler.test.js +++ /dev/null @@ -1,96 +0,0 @@ -/* eslint-disable no-unused-expressions */ -const chai = require('chai'); -const sinon = require('sinon'); -const sinonChai = require('sinon-chai'); -const handler = require('../../lib/express/handler'); - -const { expect } = chai; - -chai.use(sinonChai); - -describe('express:handler', () => { - let req; - let res; - let service; - let options; - let user; - let payload; - let accessToken = 'access'; - - beforeEach(() => { - payload = { userId: 1 }; - user = { name: 'Bob' }; - options = { entity: 'user', name: 'github' }; - service = { - create: sinon.stub().returns(Promise.resolve({ accessToken })) - }; - - req = { - user, - payload, - app: { - get: sinon.stub().returns({ path: '/authentication' }), - service: sinon.stub().returns(service) - } - }; - res = {}; - }); - - afterEach(() => { - req.app.service.reset(); - service.create.reset(); - }); - - it('calls create on the authentication service', done => { - const params = { - authenticated: true, - payload, - user - }; - - handler(options)(req, res, () => { - expect(req.app.service).to.have.been.calledOnce; - expect(req.app.service).to.have.been.calledWith('/authentication'); - expect(service.create).to.have.been.calledOnce; - expect(service.create).to.have.been.calledWith({ user, payload }, params); - done(); - }); - }); - - describe('when create succeeds', () => { - it('sets res.data', done => { - handler(options)(req, res, () => { - expect(res.data).to.deep.equal({ accessToken }); - done(); - }); - }); - - it('calls next', done => { - handler(options)(req, res, done); - }); - - describe('when successRedirect is set', () => { - it('sets the redirect object on the request', done => { - options.successRedirect = '/app'; - handler(options)(req, res, () => { - expect(res.hook.data.__redirect).to.deep.equal({ status: 302, url: options.successRedirect }); - done(); - }); - }); - }); - }); - - describe('when create fails', () => { - beforeEach(() => { - service.create = sinon.stub().returns(Promise.reject(new Error('Auth Error'))); - req.app.service = sinon.stub().returns(service); - }); - - it('calls next with an error', done => { - handler(options)(req, res, error => { - expect(error).to.not.equal(undefined); - done(); - }); - }); - }); -}); diff --git a/packages/authentication-oauth1/test/fixtures/strategy.js b/packages/authentication-oauth1/test/fixtures/strategy.js deleted file mode 100644 index 58142848d4..0000000000 --- a/packages/authentication-oauth1/test/fixtures/strategy.js +++ /dev/null @@ -1,41 +0,0 @@ -const passport = require('passport-strategy'); -const util = require('util'); - -function Strategy (options, verify) { - passport.Strategy.call(this); - this.name = 'mock'; - this._options = options; - this._verify = verify; -} - -util.inherits(Strategy, passport.Strategy); - -Strategy.prototype.authenticate = function (req, options) { - const accessToken = 'mockAccessToken'; - const refreshToken = 'mockRefreshToken'; - const profile = { name: 'Mocky Mockerson' }; - - const callback = function (error, user, info) { - if (error) { - return this.error(error); - } - - if (info && info.pass) { - return this.pass(); - } - - if (info && info.url) { - return this.redirect(info.url, info.status); - } - - if (!user) { - return this.fail(info.challenge, info.status); - } - - return this.success(user, info); - }.bind(this); - - this._verify(req, accessToken, refreshToken, profile, callback); -}; - -module.exports = Strategy; diff --git a/packages/authentication-oauth1/test/index.test.js b/packages/authentication-oauth1/test/index.test.js deleted file mode 100644 index 7ed3393775..0000000000 --- a/packages/authentication-oauth1/test/index.test.js +++ /dev/null @@ -1,290 +0,0 @@ -/* eslint-disable no-unused-expressions */ -const feathers = require('@feathersjs/feathers'); -const authentication = require('@feathersjs/authentication'); -const expressify = require('@feathersjs/express'); -const memory = require('feathers-memory'); -const chai = require('chai'); -const sinon = require('sinon'); -const sinonChai = require('sinon-chai'); - -const oauth1 = require('../lib'); -const Strategy = require('./fixtures/strategy'); - -const { Verifier } = oauth1; -const { expect } = chai; - -chai.use(sinonChai); - -describe('@feathersjs/authentication-oauth1', () => { - it('is CommonJS compatible', () => { - expect(typeof require('../lib')).to.equal('function'); - }); - - it('basic functionality', () => { - expect(typeof oauth1).to.equal('function'); - }); - - it('exports default', () => { - expect(oauth1.default).to.equal(oauth1); - }); - - it('exposes the Verifier class', () => { - expect(typeof Verifier).to.equal('function'); - expect(typeof oauth1.Verifier).to.equal('function'); - }); - - describe('initialization', () => { - let app; - let config; - let globalConfig; - - beforeEach(() => { - config = { - name: 'twitter', - Strategy, - consumerKey: '1234', - consumerSecret: 'secret' - }; - - globalConfig = { - secret: 'supersecret', - twitter: { - consumerKey: '1234', - consumerSecret: 'secret', - scope: ['user'] - } - }; - - app = expressify(feathers()); - app.set('host', 'localhost'); - app.set('port', 3030); - app.use('/users', memory()); - app.configure(authentication(globalConfig)); - }); - - it('throws an error if passport has not been registered', () => { - expect(() => { - expressify(feathers()).configure(oauth1()); - }).to.throw(); - }); - - it('throws an error if strategy name is missing', () => { - expect(() => { - delete config.name; - app.configure(oauth1(config)); - }).to.throw(); - }); - - it('throws an error if Strategy is missing', () => { - expect(() => { - delete config.Strategy; - app.configure(oauth1(config)); - }).to.throw(); - }); - - it('throws an error if consumerKey is missing', () => { - expect(() => { - delete config.consumerKey; - delete globalConfig.twitter.consumerKey; - expressify(feathers()).configure(authentication(globalConfig)).configure(oauth1(config)); - }).to.throw(); - }); - - it('throws an error if consumerSecret is missing', () => { - expect(() => { - delete config.consumerSecret; - delete globalConfig.twitter.consumerSecret; - expressify(feathers()).configure(authentication(globalConfig)).configure(oauth1(config)); - }).to.throw(); - }); - - it('registers the oauth1 passport strategy', () => { - sinon.spy(app.passport, 'use'); - sinon.spy(config, 'Strategy'); - app.configure(oauth1(config)); - app.setup(); - - expect(config.Strategy).to.have.been.calledOnce; - expect(app.passport.use).to.have.been.calledWith(config.name); - - app.passport.use.restore(); - config.Strategy.restore(); - }); - - it('registers the strategy options', () => { - sinon.spy(app.passport, 'options'); - app.configure(oauth1(config)); - app.setup(); - - expect(app.passport.options).to.have.been.calledOnce; - - app.passport.options.restore(); - }); - - it('registers the redirect options on strategy options', () => { - sinon.spy(authentication.express, 'authenticate'); - const mergedOptions = Object.assign({}, config, globalConfig); - app.configure(oauth1(mergedOptions)); - app.setup(); - - delete mergedOptions.Strategy; - expect(authentication.express.authenticate).to.have.been.calledWith(config.name, sinon.match(mergedOptions)); - - authentication.express.authenticate.restore(); - }); - - describe('passport strategy options', () => { - let authOptions; - let args; - - beforeEach(() => { - config.custom = true; - sinon.spy(config, 'Strategy'); - app.configure(oauth1(config)); - app.setup(); - authOptions = app.get('authentication'); - args = config.Strategy.getCall(0).args[0]; - }); - - afterEach(() => { - config.Strategy.restore(); - }); - - it('sets path', () => { - expect(args.path).to.equal(`/auth/${config.name}`); - }); - - it('sets callbackPath', () => { - expect(args.callbackPath).to.equal(`/auth/${config.name}/callback`); - }); - - it('sets callbackURL', () => { - expect(args.callbackURL).to.equal(`http://localhost:3030/auth/${config.name}/callback`); - }); - - it('sets idField', () => { - expect(args.idField).to.equal(`${config.name}Id`); - }); - - it('sets entity', () => { - expect(args.entity).to.equal(authOptions.entity); - }); - - it('sets service', () => { - expect(args.service).to.equal(authOptions.service); - }); - - it('sets session', () => { - expect(args.session).to.equal(true); - }); - - it('sets passReqToCallback', () => { - expect(args.passReqToCallback).to.equal(authOptions.passReqToCallback); - }); - - it('supports setting custom options', () => { - expect(args.custom).to.equal(true); - }); - }); - - it('mixes in global config for strategy', () => { - delete config.consumerKey; - delete config.consumerSecret; - sinon.spy(config, 'Strategy'); - - app.configure(oauth1(config)); - app.setup(); - - expect(config.Strategy.getCall(0).args[0].scope).to.deep.equal(['user']); - - config.Strategy.restore(); - }); - - it('supports overriding default options', () => { - sinon.spy(config, 'Strategy'); - config.entity = 'organization'; - app.configure(oauth1(config)); - app.setup(); - - expect(config.Strategy.getCall(0).args[0].entity).to.equal('organization'); - - config.Strategy.restore(); - }); - - it('registers express get route', () => { - sinon.spy(app, 'get'); - app.configure(oauth1(config)); - app.setup(); - - expect(app.get).to.have.been.calledWith(`/auth/${config.name}`); - - app.get.restore(); - }); - - it('registers express callback route', () => { - sinon.spy(app, 'get'); - app.configure(oauth1(config)); - app.setup(); - - expect(app.get).to.have.been.calledWith(`/auth/${config.name}/callback`); - - app.get.restore(); - }); - - it('registers custom express callback route', () => { - sinon.spy(app, 'get'); - config.callbackPath = `/v1/api/auth/${config.name}/callback`; - app.configure(oauth1(config)); - app.setup(); - - expect(app.get).to.have.been.calledWith(config.callbackPath); - - app.get.restore(); - }); - - describe('custom Verifier', () => { - it('throws an error if a verify function is missing', () => { - expect(() => { - class CustomVerifier { - constructor (app) { - this.app = app; - } - } - config.Verifier = CustomVerifier; - app.configure(oauth1(config)); - app.setup(); - }).to.throw(); - }); - - it('verifies through custom verify function', () => { - const User = { - email: 'admin@feathersjs.com', - password: 'password' - }; - - const req = { - query: {}, - body: Object.assign({}, User), - headers: {}, - cookies: {} - }; - class CustomVerifier extends Verifier { - verify (req, accessToken, refreshToken, profile, done) { - expect(accessToken).to.equal('mockAccessToken'); - expect(refreshToken).to.equal('mockRefreshToken'); - expect(profile).to.deep.equal({ name: 'Mocky Mockerson' }); - done(null, User); - } - } - - config.Verifier = CustomVerifier; - app.configure(oauth1(config)); - app.setup(); - - return app.authenticate('twitter')(req).then(result => { - expect(result.data.user).to.deep.equal(User); - }); - }); - }); - }); -}); diff --git a/packages/authentication-oauth1/test/verifier.test.js b/packages/authentication-oauth1/test/verifier.test.js deleted file mode 100644 index e096835cb0..0000000000 --- a/packages/authentication-oauth1/test/verifier.test.js +++ /dev/null @@ -1,331 +0,0 @@ -/* eslint-disable no-unused-expressions */ -const feathers = require('@feathersjs/feathers'); -const expressify = require('@feathersjs/express'); -const authentication = require('@feathersjs/authentication'); - -const { Verifier } = require('../lib'); - -const chai = require('chai'); -const sinon = require('sinon'); -const sinonChai = require('sinon-chai'); - -const { expect } = chai; - -chai.use(sinonChai); - -describe('Verifier', () => { - let service; - let app; - let options; - let verifier; - let user; - - beforeEach(() => { - user = { id: 'test', email: 'admin@feathersjs.com' }; - service = { - id: 'id', - find: sinon.stub().returns(Promise.resolve([user])), - create: sinon.stub().returns(Promise.resolve(user)), - patch: sinon.stub().returns(Promise.resolve(user)) - }; - - app = expressify(feathers()); - app.use('users', service) - .configure(authentication({ secret: 'supersecret' })); - - options = app.get('authentication'); - options.name = 'twitter'; - options.idField = 'twitterId'; - - verifier = new Verifier(app, options); - }); - - it('is CommonJS compatible', () => { - expect(typeof require('../lib/verifier')).to.equal('function'); - }); - - it('exposes the Verifier class', () => { - expect(typeof Verifier).to.equal('function'); - }); - - describe('constructor', () => { - it('retains an app reference', () => { - expect(verifier.app).to.deep.equal(app); - }); - - it('sets options', () => { - expect(verifier.options).to.deep.equal(options); - }); - - it('sets service using service path', () => { - expect(verifier.service).to.deep.equal(app.service('users')); - }); - - it('sets a passed in service instance', () => { - options.service = service; - expect(new Verifier(app, options).service).to.deep.equal(service); - }); - - describe('when service is undefined', () => { - it('throws an error', () => { - expect(() => { - new Verifier(app, {}); // eslint-disable-line - }).to.throw(); - }); - }); - }); - - describe('_updateEntity', () => { - let entity; - let data; - let args; - - beforeEach(() => { - entity = { id: 1, name: 'Admin' }; - data = { - accessToken: 'access', - refreshToken: 'refresh', - profile: { - id: 1234, - name: 'Admin' - } - }; - return verifier._updateEntity(entity, data).then(() => { - args = service.patch.getCall(0).args; - }); - }); - - it('calls patch on passed in service', () => { - expect(service.patch).to.have.been.calledOnce; - }); - - it('passes id', () => { - expect(args[0]).to.equal(entity.id); - }); - - it('passes patch data', () => { - expect(args[1].twitterId).to.equal(data.profile.id); - expect(args[1].twitter).to.deep.equal(data); - }); - - it('passes oauth provider via params', () => { - expect(args[2]).to.deep.equal({ oauth: { provider: 'twitter' } }); - }); - }); - - describe('_createEntity', () => { - let data; - let args; - - beforeEach(() => { - data = { - accessToken: 'access', - refreshToken: 'refresh', - profile: { - id: 1234, - name: 'Admin' - } - }; - return verifier._createEntity(data).then(() => { - args = service.create.getCall(0).args; - }); - }); - - it('calls create on passed in service', () => { - expect(service.create).to.have.been.calledOnce; - }); - - it('passes entity', () => { - expect(args[0].twitterId).to.equal(data.profile.id); - expect(args[0].twitter).to.deep.equal(data); - }); - - it('passes oauth provider via params', () => { - expect(args[1]).to.deep.equal({ oauth: { provider: 'twitter' } }); - }); - }); - - describe('_normalizeResult', () => { - describe('when has results', () => { - it('returns entity when paginated', () => { - return verifier._normalizeResult({ data: [user] }).then(result => { - expect(result).to.deep.equal(user); - }); - }); - - it('returns entity when not paginated', () => { - return verifier._normalizeResult([user]).then(result => { - expect(result).to.deep.equal(user); - }); - }); - - it('calls toObject on entity when present', () => { - user.toObject = sinon.spy(); - return verifier._normalizeResult({ data: [user] }).then(() => { - expect(user.toObject).to.have.been.calledOnce; - }); - }); - - it('calls toJSON on entity when present', () => { - user.toJSON = sinon.spy(); - return verifier._normalizeResult({ data: [user] }).then(() => { - expect(user.toJSON).to.have.been.calledOnce; - }); - }); - }); - - describe('when no results', () => { - it('rejects with false when paginated', () => { - return verifier._normalizeResult({ data: [] }).catch(error => { - expect(error).to.equal(false); - }); - }); - - it('rejects with false when not paginated', () => { - return verifier._normalizeResult([]).catch(error => { - expect(error).to.equal(false); - }); - }); - }); - }); - - describe('verify', () => { - it('calls find on the provided service', done => { - verifier.verify({}, 'access', 'refresh', { id: 1234 }, () => { - const query = { twitterId: 1234, $limit: 1 }; - expect(service.find).to.have.been.calledOnce; - expect(service.find).to.have.been.calledWith({ query }); - done(); - }); - }); - - it('calls with query from makeQuery', done => { - options = Object.assign({}, options, { makeQuery: sinon.stub().returns({ key: 'value' }) }); - verifier = new Verifier(app, options); - verifier.verify({}, 'access', 'refresh', { id: 1234 }, () => { - const query = { key: 'value', $limit: 1 }; - expect(options.makeQuery).to.have.been.calledOnce; - expect(service.find).to.have.been.calledWith({ query }); - done(); - }); - }); - - it('calls _normalizeResult', done => { - sinon.spy(verifier, '_normalizeResult'); - verifier.verify({}, 'access', 'refresh', { id: 1234 }, () => { - expect(verifier._normalizeResult).to.have.been.calledOnce; - verifier._normalizeResult.restore(); - done(); - }); - }); - - describe('when entity exists on request object', () => { - it('calls _updateEntity', done => { - sinon.spy(verifier, '_updateEntity'); - const req = { 'user': { name: 'Admin' } }; - verifier.verify(req, 'access', 'refresh', { id: 1234 }, () => { - expect(verifier._updateEntity).to.have.been.calledOnce; - verifier._updateEntity.restore(); - done(); - }); - }); - }); - - describe('when entity exists on request.params object', () => { - it('calls _updateEntity', done => { - sinon.spy(verifier, '_updateEntity'); - const req = { - params: { - 'user': { name: 'Admin' } - } - }; - verifier.verify(req, 'access', 'refresh', { id: 1234 }, () => { - expect(verifier._updateEntity).to.have.been.calledOnce; - verifier._updateEntity.restore(); - done(); - }); - }); - }); - - it('calls _createEntity when entity not found', done => { - sinon.spy(verifier, '_createEntity'); - verifier._normalizeResult = () => Promise.resolve(null); - verifier.verify({}, 'access', 'refresh', { id: 1234 }, () => { - expect(verifier._createEntity).to.have.been.calledOnce; - verifier._createEntity.restore(); - done(); - }); - }); - - it('calls _updateEntity when entity is found', done => { - sinon.spy(verifier, '_updateEntity'); - verifier.verify({}, 'access', 'refresh', { id: 1234 }, () => { - expect(verifier._updateEntity).to.have.been.calledOnce; - verifier._updateEntity.restore(); - done(); - }); - }); - - it('returns the entity', done => { - verifier.verify({}, 'access', 'refresh', { id: 1234 }, (error, entity) => { - expect(error).to.equal(null); - expect(entity).to.deep.equal(user); - done(); - }); - }); - - it('handles false rejections in promise chain', done => { - verifier._updateEntity = () => Promise.reject(false); // eslint-disable-line - verifier.verify({}, 'access', 'refresh', { id: 1234 }, (error, entity) => { - expect(error).to.equal(null); - expect(entity).to.equal(false); - done(); - }); - }); - - it('returns errors', done => { - const authError = new Error('An error'); - verifier._normalizeResult = () => Promise.reject(authError); - verifier.verify({}, 'access', 'refresh', { id: 1234 }, (error, entity) => { - expect(error).to.equal(authError); - expect(entity).to.equal(undefined); - done(); - }); - }); - }); -}); - -describe('Verifier without service.id', function () { - let service; - let app; - let options; - let verifier; - let user; - - beforeEach(() => { - user = { id: 1, email: 'admin@feathersjs.com' }; - service = { - find: sinon.stub().returns(Promise.resolve([user])), - create: sinon.stub().returns(Promise.resolve(user)), - patch: sinon.stub().returns(Promise.resolve(user)) - }; - - app = expressify(feathers()); - app.use('users', service) - .configure(authentication({ secret: 'supersecret' })); - - options = app.get('authentication'); - options.name = 'twitter'; - options.idField = 'twitterId'; - - verifier = new Verifier(app, options); - }); - - it('throws an error when service.id is not set', done => { - verifier.verify({}, 'access', 'refresh', { id: 1234 }, (error, entity) => { - expect(error.message.includes('the `id` property must be set')).to.equal(true); - expect(entity).to.equal(undefined); - done(); - }); - }); -}); diff --git a/packages/authentication-oauth2/.npmignore b/packages/authentication-oauth2/.npmignore deleted file mode 100644 index 65e3ba2eda..0000000000 --- a/packages/authentication-oauth2/.npmignore +++ /dev/null @@ -1 +0,0 @@ -test/ diff --git a/packages/authentication-oauth2/CHANGELOG.md b/packages/authentication-oauth2/CHANGELOG.md deleted file mode 100644 index 5d86872f7e..0000000000 --- a/packages/authentication-oauth2/CHANGELOG.md +++ /dev/null @@ -1,310 +0,0 @@ -# Change Log - -All notable changes to this project will be documented in this file. -See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -## [1.3.1](https://github.com/feathersjs/feathers/compare/@feathersjs/authentication-oauth2@1.3.0...@feathersjs/authentication-oauth2@1.3.1) (2019-01-26) - - -### Bug Fixes - -* **authentication:** Fall back when req.app is not the application when emitting events ([#1185](https://github.com/feathersjs/feathers/issues/1185)) ([6a534f0](https://github.com/feathersjs/feathers/commit/6a534f0)) - - - - - -# [1.3.0](https://github.com/feathersjs/feathers/compare/@feathersjs/authentication-oauth2@1.2.7...@feathersjs/authentication-oauth2@1.3.0) (2019-01-06) - - -### Features - -* Make custom query for oAuth authentication ([#1124](https://github.com/feathersjs/feathers/issues/1124)) ([5d43e3c](https://github.com/feathersjs/feathers/commit/5d43e3c)) - - - - - -## [1.2.7](https://github.com/feathersjs/feathers/compare/@feathersjs/authentication-oauth2@1.2.6...@feathersjs/authentication-oauth2@1.2.7) (2019-01-02) - - -### Bug Fixes - -* Update adapter common tests ([#1135](https://github.com/feathersjs/feathers/issues/1135)) ([8166dda](https://github.com/feathersjs/feathers/commit/8166dda)) - - - - - - -## [1.2.6](https://github.com/feathersjs/feathers/compare/@feathersjs/authentication-oauth2@1.2.5...@feathersjs/authentication-oauth2@1.2.6) (2018-12-16) - -**Note:** Version bump only for package @feathersjs/authentication-oauth2 - - - - - - -## [1.2.5](https://github.com/feathersjs/feathers/compare/@feathersjs/authentication-oauth2@1.2.4...@feathersjs/authentication-oauth2@1.2.5) (2018-10-25) - - -### Bug Fixes - -* Make Mocha a proper devDependency for every repository ([#1053](https://github.com/feathersjs/feathers/issues/1053)) ([9974803](https://github.com/feathersjs/feathers/commit/9974803)) - - - - - - -## [1.2.4](https://github.com/feathersjs/feathers/compare/@feathersjs/authentication-oauth2@1.2.3...@feathersjs/authentication-oauth2@1.2.4) (2018-09-21) - -**Note:** Version bump only for package @feathersjs/authentication-oauth2 - - - - - - -## [1.2.3](https://github.com/feathersjs/feathers/compare/@feathersjs/authentication-oauth2@1.2.2...@feathersjs/authentication-oauth2@1.2.3) (2018-09-17) - -**Note:** Version bump only for package @feathersjs/authentication-oauth2 - - - - - - -## [1.2.2](https://github.com/feathersjs/feathers/compare/@feathersjs/authentication-oauth2@1.2.1...@feathersjs/authentication-oauth2@1.2.2) (2018-09-02) - -**Note:** Version bump only for package @feathersjs/authentication-oauth2 - - -## 1.2.1 - -- Migrate to Monorepo ([feathers#462](https://github.com/feathersjs/feathers/issues/462)) - -## [v1.2.0](https://github.com/feathersjs/authentication-oauth2/tree/v1.2.0) (2018-08-28) -[Full Changelog](https://github.com/feathersjs/authentication-oauth2/compare/v1.1.0...v1.2.0) - -**Closed issues:** - -- Entity could be an array? [\#87](https://github.com/feathersjs/authentication-oauth2/issues/87) -- Cannot use Passport-Google-Token through POST request on /authentication [\#83](https://github.com/feathersjs/authentication-oauth2/issues/83) -- JWT sub is always 'anonymous' after using verifier [\#82](https://github.com/feathersjs/authentication-oauth2/issues/82) -- How to authenticate token from Facebook on Feathers server [\#80](https://github.com/feathersjs/authentication-oauth2/issues/80) -- How to use FBSDK Login with React Native and FeathersJS? [\#22](https://github.com/feathersjs/authentication-oauth2/issues/22) -- Create custom formatter to support calling back a mobile deep link [\#8](https://github.com/feathersjs/authentication-oauth2/issues/8) -- Create custom formatter for returning an html page that calls back to parent window [\#7](https://github.com/feathersjs/authentication-oauth2/issues/7) - -**Merged pull requests:** - -- Update all dependencies [\#89](https://github.com/feathersjs/authentication-oauth2/pull/89) ([daffl](https://github.com/daffl)) -- Allowing return type of \_createEntity to be an Array [\#88](https://github.com/feathersjs/authentication-oauth2/pull/88) ([testless](https://github.com/testless)) -- Fix for Update an existing entity in verifier \#18 [\#86](https://github.com/feathersjs/authentication-oauth2/pull/86) ([nsainaney](https://github.com/nsainaney)) -- Update @feathersjs/commons to the latest version 🚀 [\#84](https://github.com/feathersjs/authentication-oauth2/pull/84) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) -- Fix state=true in authorizationURL integrating passport-oauth2 [\#81](https://github.com/feathersjs/authentication-oauth2/pull/81) ([happydenn](https://github.com/happydenn)) - -## [v1.1.0](https://github.com/feathersjs/authentication-oauth2/tree/v1.1.0) (2018-06-23) -[Full Changelog](https://github.com/feathersjs/authentication-oauth2/compare/v1.0.3...v1.1.0) - -**Closed issues:** - -- Authentication always logging as first user [\#77](https://github.com/feathersjs/authentication-oauth2/issues/77) -- Restricting certain email domains [\#75](https://github.com/feathersjs/authentication-oauth2/issues/75) -- Error when save callback payload google login into users service [\#74](https://github.com/feathersjs/authentication-oauth2/issues/74) -- Confusing in doc of 'OAuth2 Authentication' [\#73](https://github.com/feathersjs/authentication-oauth2/issues/73) -- Deleted [\#71](https://github.com/feathersjs/authentication-oauth2/issues/71) -- Callback URL problem in production when using oauth [\#53](https://github.com/feathersjs/authentication-oauth2/issues/53) -- Unable to override Facebook display options [\#32](https://github.com/feathersjs/authentication-oauth2/issues/32) -- Implement oauth2 with graphql [\#25](https://github.com/feathersjs/authentication-oauth2/issues/25) - -**Merged pull requests:** - -- Allow a custom error Handler [\#79](https://github.com/feathersjs/authentication-oauth2/pull/79) ([mrdj07](https://github.com/mrdj07)) -- Update sinon to the latest version 🚀 [\#76](https://github.com/feathersjs/authentication-oauth2/pull/76) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) -- Update sinon to the latest version 🚀 [\#72](https://github.com/feathersjs/authentication-oauth2/pull/72) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) -- Update sinon-chai to the latest version 🚀 [\#69](https://github.com/feathersjs/authentication-oauth2/pull/69) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) -- Update mocha to the latest version 🚀 [\#66](https://github.com/feathersjs/authentication-oauth2/pull/66) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) - -## [v1.0.3](https://github.com/feathersjs/authentication-oauth2/tree/v1.0.3) (2018-01-03) -[Full Changelog](https://github.com/feathersjs/authentication-oauth2/compare/v1.0.2...v1.0.3) - -**Closed issues:** - -- I got Internal error when I log in with google [\#63](https://github.com/feathersjs/authentication-oauth2/issues/63) -- how to use proxy when call authnetication provider [\#62](https://github.com/feathersjs/authentication-oauth2/issues/62) - -**Merged pull requests:** - -- Update documentation to correspond with latest release [\#65](https://github.com/feathersjs/authentication-oauth2/pull/65) ([daffl](https://github.com/daffl)) -- Update semistandard to the latest version 🚀 [\#64](https://github.com/feathersjs/authentication-oauth2/pull/64) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) -- Update feathers-memory to the latest version 🚀 [\#61](https://github.com/feathersjs/authentication-oauth2/pull/61) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) - -## [v1.0.2](https://github.com/feathersjs/authentication-oauth2/tree/v1.0.2) (2017-11-28) -[Full Changelog](https://github.com/feathersjs/authentication-oauth2/compare/v1.0.1...v1.0.2) - -**Closed issues:** - -- Dependency @feathersjs/express not declared [\#59](https://github.com/feathersjs/authentication-oauth2/issues/59) - -**Merged pull requests:** - -- Add @feathersjs/express dependency [\#60](https://github.com/feathersjs/authentication-oauth2/pull/60) ([daffl](https://github.com/daffl)) - -## [v1.0.1](https://github.com/feathersjs/authentication-oauth2/tree/v1.0.1) (2017-11-16) -[Full Changelog](https://github.com/feathersjs/authentication-oauth2/compare/v1.0.0...v1.0.1) - -**Closed issues:** - -- `facebook.profileFields` not works properly [\#57](https://github.com/feathersjs/authentication-oauth2/issues/57) -- I get Internal server error after I auth with Google authentication [\#55](https://github.com/feathersjs/authentication-oauth2/issues/55) -- Cannot connect with OAuth2, always getting 404 Not Found Page. [\#54](https://github.com/feathersjs/authentication-oauth2/issues/54) - -**Merged pull requests:** - -- Add default export for better ES module \(TypeScript\) compatibility [\#58](https://github.com/feathersjs/authentication-oauth2/pull/58) ([daffl](https://github.com/daffl)) -- Update @feathersjs/authentication to the latest version 🚀 [\#56](https://github.com/feathersjs/authentication-oauth2/pull/56) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) - -## [v1.0.0](https://github.com/feathersjs/authentication-oauth2/tree/v1.0.0) (2017-11-01) -[Full Changelog](https://github.com/feathersjs/authentication-oauth2/compare/v1.0.0-pre.1...v1.0.0) - -**Merged pull requests:** - -- Update dependencies for release [\#52](https://github.com/feathersjs/authentication-oauth2/pull/52) ([daffl](https://github.com/daffl)) - -## [v1.0.0-pre.1](https://github.com/feathersjs/authentication-oauth2/tree/v1.0.0-pre.1) (2017-10-25) -[Full Changelog](https://github.com/feathersjs/authentication-oauth2/compare/v0.3.2...v1.0.0-pre.1) - -**Merged pull requests:** - -- Update to Feathers v3 [\#51](https://github.com/feathersjs/authentication-oauth2/pull/51) ([daffl](https://github.com/daffl)) -- Rename repository and use npm scope [\#50](https://github.com/feathersjs/authentication-oauth2/pull/50) ([daffl](https://github.com/daffl)) -- Update to new plugin infrastructure [\#49](https://github.com/feathersjs/authentication-oauth2/pull/49) ([daffl](https://github.com/daffl)) - -## [v0.3.2](https://github.com/feathersjs/authentication-oauth2/tree/v0.3.2) (2017-10-15) -[Full Changelog](https://github.com/feathersjs/authentication-oauth2/compare/v0.3.1...v0.3.2) - -**Closed issues:** - -- Examples are no longer working due to missing email property [\#40](https://github.com/feathersjs/authentication-oauth2/issues/40) -- Log a warning if this.service.id is undefined or null [\#21](https://github.com/feathersjs/authentication-oauth2/issues/21) - -**Merged pull requests:** - -- Erroring if service.id is undefined. Closes \#21. [\#47](https://github.com/feathersjs/authentication-oauth2/pull/47) ([ekryski](https://github.com/ekryski)) -- Update mocha to the latest version 🚀 [\#46](https://github.com/feathersjs/authentication-oauth2/pull/46) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) -- Update examples to work with latest OAuth payload [\#45](https://github.com/feathersjs/authentication-oauth2/pull/45) ([teddy-error](https://github.com/teddy-error)) -- Update sinon to the latest version 🚀 [\#43](https://github.com/feathersjs/authentication-oauth2/pull/43) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) - -## [v0.3.1](https://github.com/feathersjs/authentication-oauth2/tree/v0.3.1) (2017-09-27) -[Full Changelog](https://github.com/feathersjs/authentication-oauth2/compare/v0.3.0...v0.3.1) - -**Closed issues:** - -- Google "hostedDomain" not working [\#13](https://github.com/feathersjs/authentication-oauth2/issues/13) - -**Merged pull requests:** - -- Simplified redirectOptions \(\#42\) [\#44](https://github.com/feathersjs/authentication-oauth2/pull/44) ([nsainaney](https://github.com/nsainaney)) - -## [v0.3.0](https://github.com/feathersjs/authentication-oauth2/tree/v0.3.0) (2017-09-25) -[Full Changelog](https://github.com/feathersjs/authentication-oauth2/compare/v0.2.6...v0.3.0) - -**Closed issues:** - -- Missing params on OAuth redirect creation [\#41](https://github.com/feathersjs/authentication-oauth2/issues/41) -- how to custom callback page after authetication [\#39](https://github.com/feathersjs/authentication-oauth2/issues/39) -- profileUrl is undefined [\#38](https://github.com/feathersjs/authentication-oauth2/issues/38) -- Use patch to update the entity instead of update [\#31](https://github.com/feathersjs/authentication-oauth2/issues/31) -- Update existing user in verifier will change the user password hash from an already hashed password. [\#19](https://github.com/feathersjs/authentication-oauth2/issues/19) - -**Merged pull requests:** - -- Added support for redirect options on strategy options \(\#41\) [\#42](https://github.com/feathersjs/authentication-oauth2/pull/42) ([nsainaney](https://github.com/nsainaney)) -- \#19 Fix: using patch to update entity in verifier [\#20](https://github.com/feathersjs/authentication-oauth2/pull/20) ([skinnyworm](https://github.com/skinnyworm)) - -## [v0.2.6](https://github.com/feathersjs/authentication-oauth2/tree/v0.2.6) (2017-09-12) -[Full Changelog](https://github.com/feathersjs/authentication-oauth2/compare/v0.2.5...v0.2.6) - -**Closed issues:** - -- 0.2.5 introduced breaking changes [\#36](https://github.com/feathersjs/authentication-oauth2/issues/36) -- An in-range update of feathers is breaking the build 🚨 [\#34](https://github.com/feathersjs/authentication-oauth2/issues/34) - -**Merged pull requests:** - -- Fix old property name fallback for backwards compatibility [\#37](https://github.com/feathersjs/authentication-oauth2/pull/37) ([daffl](https://github.com/daffl)) -- Include babel-polyfill and package-lock.json [\#35](https://github.com/feathersjs/authentication-oauth2/pull/35) ([daffl](https://github.com/daffl)) -- Update debug to the latest version 🚀 [\#30](https://github.com/feathersjs/authentication-oauth2/pull/30) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) -- Update sinon to the latest version 🚀 [\#29](https://github.com/feathersjs/authentication-oauth2/pull/29) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) - -## [v0.2.5](https://github.com/feathersjs/authentication-oauth2/tree/v0.2.5) (2017-06-21) -[Full Changelog](https://github.com/feathersjs/authentication-oauth2/compare/v0.2.4...v0.2.5) - -**Closed issues:** - -- Module is using the wrong default config key [\#23](https://github.com/feathersjs/authentication-oauth2/issues/23) -- Generated default.json is missing scope for Google OAuth [\#14](https://github.com/feathersjs/authentication-oauth2/issues/14) -- Cookie not getting set [\#12](https://github.com/feathersjs/authentication-oauth2/issues/12) - -**Merged pull requests:** - -- Greenkeeper/chai 4.0.2 [\#28](https://github.com/feathersjs/authentication-oauth2/pull/28) ([daffl](https://github.com/daffl)) -- using correct auth key. Closes 23. [\#24](https://github.com/feathersjs/authentication-oauth2/pull/24) ([ekryski](https://github.com/ekryski)) -- Update semistandard to the latest version 🚀 [\#17](https://github.com/feathersjs/authentication-oauth2/pull/17) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) -- Update feathers-hooks to the latest version 🚀 [\#16](https://github.com/feathersjs/authentication-oauth2/pull/16) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) -- Update dependencies to enable Greenkeeper 🌴 [\#15](https://github.com/feathersjs/authentication-oauth2/pull/15) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) - -## [v0.2.4](https://github.com/feathersjs/authentication-oauth2/tree/v0.2.4) (2017-03-24) -[Full Changelog](https://github.com/feathersjs/authentication-oauth2/compare/v0.2.3...v0.2.4) - -**Closed issues:** - -- Support oauth2 endpoint within sub-app or reverse proxy [\#9](https://github.com/feathersjs/authentication-oauth2/issues/9) -- OAuth JWT user data [\#4](https://github.com/feathersjs/authentication-oauth2/issues/4) - -**Merged pull requests:** - -- Follow failureRedirect on oauth2 authentication failure [\#11](https://github.com/feathersjs/authentication-oauth2/pull/11) ([buske](https://github.com/buske)) -- Issue \#9: Add callbackPath option to support oauth2 endpoint within sub-app or server proxy [\#10](https://github.com/feathersjs/authentication-oauth2/pull/10) ([buske](https://github.com/buske)) - -## [v0.2.3](https://github.com/feathersjs/authentication-oauth2/tree/v0.2.3) (2016-12-14) -[Full Changelog](https://github.com/feathersjs/authentication-oauth2/compare/v0.2.2...v0.2.3) - -## [v0.2.2](https://github.com/feathersjs/authentication-oauth2/tree/v0.2.2) (2016-12-14) -[Full Changelog](https://github.com/feathersjs/authentication-oauth2/compare/v0.2.1...v0.2.2) - -**Merged pull requests:** - -- fixing success and failure redirects [\#6](https://github.com/feathersjs/authentication-oauth2/pull/6) ([ekryski](https://github.com/ekryski)) - -## [v0.2.1](https://github.com/feathersjs/authentication-oauth2/tree/v0.2.1) (2016-12-14) -[Full Changelog](https://github.com/feathersjs/authentication-oauth2/compare/v0.2.0...v0.2.1) - -## [v0.2.0](https://github.com/feathersjs/authentication-oauth2/tree/v0.2.0) (2016-11-23) -[Full Changelog](https://github.com/feathersjs/authentication-oauth2/compare/v0.1.2...v0.2.0) - -**Merged pull requests:** - -- adding compatibility with payload generation [\#3](https://github.com/feathersjs/authentication-oauth2/pull/3) ([ekryski](https://github.com/ekryski)) - -## [v0.1.2](https://github.com/feathersjs/authentication-oauth2/tree/v0.1.2) (2016-11-20) -[Full Changelog](https://github.com/feathersjs/authentication-oauth2/compare/v0.1.1...v0.1.2) - -**Merged pull requests:** - -- include feathers-commons and use omit, pick, and makeUrl from that. [\#2](https://github.com/feathersjs/authentication-oauth2/pull/2) ([ekryski](https://github.com/ekryski)) - -## [v0.1.1](https://github.com/feathersjs/authentication-oauth2/tree/v0.1.1) (2016-11-19) -[Full Changelog](https://github.com/feathersjs/authentication-oauth2/compare/v0.1.0...v0.1.1) - -**Merged pull requests:** - -- pulling options for strategy from global auth config [\#1](https://github.com/feathersjs/authentication-oauth2/pull/1) ([ekryski](https://github.com/ekryski)) - -## [v0.1.0](https://github.com/feathersjs/authentication-oauth2/tree/v0.1.0) (2016-11-16) - - -\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* diff --git a/packages/authentication-oauth2/LICENSE b/packages/authentication-oauth2/LICENSE deleted file mode 100644 index 6bfc0adefc..0000000000 --- a/packages/authentication-oauth2/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2018 Feathers - -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/packages/authentication-oauth2/README.md b/packages/authentication-oauth2/README.md deleted file mode 100644 index 11689091d8..0000000000 --- a/packages/authentication-oauth2/README.md +++ /dev/null @@ -1,57 +0,0 @@ -# @feathersjs/authentication-oauth2 - -[![Build Status](https://travis-ci.org/feathersjs/feathers.png?branch=master)](https://travis-ci.org/feathersjs/feathers) -[![Dependency Status](https://img.shields.io/david/feathersjs/feathers.svg?style=flat-square&path=packages/authentication-oauth2)](https://david-dm.org/feathersjs/feathers?path=packages/authentication-oauth2) -[![Download Status](https://img.shields.io/npm/dm/@feathersjs/authentication-oauth2.svg?style=flat-square)](https://www.npmjs.com/package/@feathersjs/authentication-oauth2) - -> An OAuth2 authentication strategy for feathers-authentication using Passport - -## Installation - -``` -npm install @feathersjs/authentication-oauth2 --save -``` - -**Note:** This is only compatibile with `feathers-authentication@1.x` and above. - -## Quick example - -```js -const feathers = require('@feathersjs/feathers'); -const authentication = require('feathers-authentication'); -const jwt = require('feathers-authentication-jwt'); -const oauth2 = require('@feathersjs/authentication-oauth2'); -const FacebookStrategy = require('passport-facebook').Strategy; -const app = feathers(); - -// Setup authentication -app.configure(authentication(settings)); -app.configure(jwt()); -app.configure(oauth2({ - name: 'facebook', - Strategy: FacebookStrategy, - clientID: '', - clientSecret: '', - scope: ['public_profile', 'email'] -})); - -// Setup a hook to only allow valid JWTs to authenticate -// and get new JWT access tokens -app.service('authentication').hooks({ - before: { - create: [ - authentication.hooks.authenticate(['jwt']) - ] - } -}); -``` - -## Documentation - -Please refer to the [@feathersjs/authentication-oauth2 API documentation](https://docs.feathersjs.com/api/authentication/oauth2.html) for more details. - -## License - -Copyright (c) 2018 - -Licensed under the [MIT license](LICENSE). diff --git a/packages/authentication-oauth2/lib/express/error-handler.js b/packages/authentication-oauth2/lib/express/error-handler.js deleted file mode 100644 index cdb6211806..0000000000 --- a/packages/authentication-oauth2/lib/express/error-handler.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = function OAuthErrorHandler (options = {}) { - return function (err, req, res, next) { - // Set __redirect so that later middleware (e.g., auth.express.failureRedirect) can redirect accordingly - if (options.failureRedirect) { - res.hook = { data: {} }; - Object.defineProperty(res.hook.data, '__redirect', { value: { status: 302, url: options.failureRedirect } }); - } - - next(err); - }; -}; diff --git a/packages/authentication-oauth2/lib/express/handler.js b/packages/authentication-oauth2/lib/express/handler.js deleted file mode 100644 index e4f1a1254b..0000000000 --- a/packages/authentication-oauth2/lib/express/handler.js +++ /dev/null @@ -1,34 +0,0 @@ -const Debug = require('debug'); - -const debug = Debug('@feathersjs/authentication-oauth2:handler'); - -module.exports = function OAuthHandler (options = {}) { - return function (req, res, next) { - const app = req.app; - const authSettings = app.get('auth') || app.get('authentication') || {}; - const entity = req[options.entity]; - const payload = req.payload; - const params = { - authenticated: true, - [options.entity]: entity, - payload - }; - const data = { - [options.entity]: entity, - payload - }; - - debug(`Executing '${options.name}' OAuth Callback`); - debug(`Calling create on '${authSettings.path}' service with`, entity); - app.service(authSettings.path).create(data, params).then(result => { - res.data = result; - - if (options.successRedirect) { - res.hook = { data: {} }; - Object.defineProperty(res.hook.data, '__redirect', { value: { status: 302, url: options.successRedirect } }); - } - - next(); - }).catch(next); - }; -}; diff --git a/packages/authentication-oauth2/lib/index.js b/packages/authentication-oauth2/lib/index.js deleted file mode 100644 index 32bc420935..0000000000 --- a/packages/authentication-oauth2/lib/index.js +++ /dev/null @@ -1,125 +0,0 @@ -const Debug = require('debug'); -const auth = require('@feathersjs/authentication'); - -const { rest } = require('@feathersjs/express'); -const { _, makeUrl } = require('@feathersjs/commons'); - -const merge = require('lodash.merge'); -const defaultHandler = require('./express/handler'); -const defaultErrorHandler = require('./express/error-handler'); -const DefaultVerifier = require('./verifier'); - -const debug = Debug('@feathersjs/authentication-oauth2'); - -const { omit, pick } = _; -const INCLUDE_KEYS = [ - 'entity', - 'service', - 'passReqToCallback', - 'session' -]; - -const EXCLUDE_KEYS = ['Verifier', 'Strategy', 'formatter']; - -// When the OAuth callback is called, req.user will always be null -// The following extracts the user from the jwt cookie if present -// This ensures that the social link happens on an existing user -function _callbackAuthenticator (config) { - return function (req, res, next) { - auth.express.authenticate('jwt', config)(req, res, () => { - // We have to mark this as unauthenticated even though req.user may be set - // because we still need the OAuth strategy to run in next() - req.authenticated = false; - next(); - }); - }; -} - -function init (options = {}) { - return function oauth2Auth () { - const app = this; - const _super = app.setup; - - if (!app.passport) { - throw new Error(`Can not find app.passport. Did you initialize feathers-authentication before @feathersjs/authentication-oauth2?`); - } - - let { name, Strategy } = options; - - if (!name) { - throw new Error(`You must provide a strategy 'name'.`); - } - - if (!Strategy) { - throw new Error(`You must provide a passport 'Strategy' instance.`); - } - - const authSettings = app.get('auth') || app.get('authentication') || {}; - - // Attempt to pull options from the global auth config - // for this provider. - const providerSettings = authSettings[name] || {}; - const oauth2Settings = merge({ - idField: `${name}Id`, - path: `/auth/${name}`, - __oauth: true - }, pick(authSettings, ...INCLUDE_KEYS), providerSettings, omit(options, ...EXCLUDE_KEYS)); - - // Set callback defaults based on provided path - oauth2Settings.callbackPath = oauth2Settings.callbackPath || `${oauth2Settings.path}/callback`; - oauth2Settings.callbackURL = oauth2Settings.callbackURL || makeUrl(oauth2Settings.callbackPath, app); - - if (!oauth2Settings.clientID) { - throw new Error(`You must provide a 'clientID' in your authentication configuration or pass one explicitly`); - } - - if (!oauth2Settings.clientSecret) { - throw new Error(`You must provide a 'clientSecret' in your authentication configuration or pass one explicitly`); - } - - const Verifier = options.Verifier || DefaultVerifier; - const formatter = options.formatter || rest.formatter; - const handler = options.handler || defaultHandler(oauth2Settings); - const errorHandler = typeof options.errorHandler === 'function' ? options.errorHandler(oauth2Settings) : defaultErrorHandler(oauth2Settings); - - // register OAuth middleware - debug(`Registering '${name}' Express OAuth middleware`); - app.get(oauth2Settings.path, auth.express.authenticate(name, omit(oauth2Settings, 'state'))); - app.get( - oauth2Settings.callbackPath, - _callbackAuthenticator(authSettings), - auth.express.authenticate(name, omit(oauth2Settings, 'state')), - handler, - errorHandler, - auth.express.emitEvents(authSettings, app), - auth.express.setCookie(authSettings), - auth.express.successRedirect(), - auth.express.failureRedirect(authSettings), - formatter - ); - - app.setup = function () { - let result = _super.apply(this, arguments); - let verifier = new Verifier(app, oauth2Settings); - - if (!verifier.verify) { - throw new Error(`Your verifier must implement a 'verify' function. It should have the same signature as a oauth2 passport verify callback.`); - } - - // Register 'oauth2' strategy with passport - debug('Registering oauth2 authentication strategy with options:', oauth2Settings); - app.passport.use(name, new Strategy(oauth2Settings, verifier.verify.bind(verifier))); - app.passport.options(name, oauth2Settings); - - return result; - }; - }; -} - -module.exports = init; - -// Exposed Modules -Object.assign(module.exports, { - default: init, - Verifier: DefaultVerifier -}); diff --git a/packages/authentication-oauth2/lib/verifier.js b/packages/authentication-oauth2/lib/verifier.js deleted file mode 100644 index 710cf7651a..0000000000 --- a/packages/authentication-oauth2/lib/verifier.js +++ /dev/null @@ -1,122 +0,0 @@ -const Debug = require('debug'); - -const debug = Debug('@feathersjs/authentication-oauth2:verify'); - -class OAuth2Verifier { - constructor (app, options = {}) { - this.app = app; - this.options = options; - this.service = typeof options.service === 'string' ? app.service(options.service) : options.service; - - options.makeQuery = options.makeQuery || function (profile, options) { - return { [options.idField]: profile.id }; // facebookId: profile.id - }; - - if (!this.service) { - throw new Error(`options.service does not exist.\n\tMake sure you are passing a valid service path or service instance and it is initialized before @feathersjs/authentication-oauth2.`); - } - - this._createEntity = this._createEntity.bind(this); - this._updateEntity = this._updateEntity.bind(this); - this._normalizeResult = this._normalizeResult.bind(this); - this.verify = this.verify.bind(this); - } - - _normalizeResult (results) { - // Paginated services return the array of results in the data attribute. - let entities = results.data ? results.data : results; - let entity = entities[0]; - - // Handle entity not found. - if (!entity) { - return Promise.resolve(null); - } - - // Handle updating mongoose models - if (typeof entity.toObject === 'function') { - entity = entity.toObject(); - } else if (typeof entity.toJSON === 'function') { - // Handle updating Sequelize models - entity = entity.toJSON(); - } - - debug(`${this.options.entity} found`); - return Promise.resolve(entity); - } - - _updateEntity (entity, data) { - const options = this.options; - const name = options.name; - const id = entity[this.service.id]; - debug(`Updating ${options.entity}: ${id}`); - - const newData = { - [options.idField]: data.profile.id, - [name]: data - }; - - return this.service.patch(id, newData, { oauth: { provider: name } }); - } - - _createEntity (data) { - const options = this.options; - const name = options.name; - const entity = { - [options.idField]: data.profile.id, - [name]: data - }; - - const id = entity[options.idField]; - debug(`Creating new ${options.entity} with ${options.idField}: ${id}`); - - return this.service.create(entity, { oauth: { provider: name } }); - } - - _setPayloadAndDone (entity, done) { - const id = entity[this.service.id]; - const payload = { [`${this.options.entity}Id`]: id }; - done(null, entity, payload); - } - - verify (req, accessToken, refreshToken, profile, done) { - debug('Checking credentials'); - const options = this.options; - const query = Object.assign({}, options.makeQuery(profile, options), { $limit: 1 }); - const data = { profile, accessToken, refreshToken }; - let existing; - - if (this.service.id === null || this.service.id === undefined) { - debug('failed: the service.id was not set'); - return done(new Error('the `id` property must be set on the entity service for authentication')); - } - - // Check request object for an existing entity - if (req && req[options.entity]) { - existing = req[options.entity]; - } - - // Check the request that came from a hook for an existing entity - if (!existing && req && req.params && req.params[options.entity]) { - existing = req.params[options.entity]; - } - - // If there is already an entity on the request object (ie. they are - // already authenticated) attach the profile to the existing entity - // because they are likely "linking" social accounts/profiles. - if (existing) { - return this._updateEntity(existing, data) - .then(entity => this._setPayloadAndDone(entity, done)) - .catch(error => error ? done(error) : done(null, error)); - } - - // Find or create the user since they could have signed up via facebook. - this.service - .find({ query }) - .then(this._normalizeResult) - .then(entity => entity ? this._updateEntity(entity, data) : this._createEntity(data)) - .then(entity => this._setPayloadAndDone(entity, done)) - .catch(error => error ? done(error) : done(null, error)); - } -} - -module.exports = OAuth2Verifier; diff --git a/packages/authentication-oauth2/package.json b/packages/authentication-oauth2/package.json deleted file mode 100644 index d93c8bb101..0000000000 --- a/packages/authentication-oauth2/package.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "name": "@feathersjs/authentication-oauth2", - "description": "An OAuth2 authentication strategy for feathers-authentication using Passport", - "version": "1.3.1", - "homepage": "https://feathersjs.com", - "main": "lib/", - "keywords": [ - "feathers", - "feathers-plugin" - ], - "license": "MIT", - "repository": { - "type": "git", - "url": "git://github.com/feathersjs/feathers.git" - }, - "author": { - "name": "Feathers contributors", - "email": "hello@feathersjs.com", - "url": "https://feathersjs.com" - }, - "contributors": [], - "bugs": { - "url": "https://github.com/feathersjs/feathers/issues" - }, - "engines": { - "node": ">= 6" - }, - "scripts": { - "test": "mocha --opts ../../mocha.opts" - }, - "directories": { - "lib": "lib" - }, - "publishConfig": { - "access": "public" - }, - "dependencies": { - "@feathersjs/commons": "^4.0.0", - "@feathersjs/errors": "^3.3.6", - "@feathersjs/express": "^1.3.1", - "debug": "^4.1.1", - "lodash.merge": "^4.6.1" - }, - "devDependencies": { - "@feathersjs/authentication": "^2.1.16", - "@feathersjs/feathers": "^3.3.1", - "body-parser": "^1.18.3", - "chai": "^4.2.0", - "feathers-memory": "^3.0.2", - "mocha": "^5.2.0", - "passport-github": "^1.1.0", - "passport-strategy": "^1.0.0", - "sinon": "^7.2.3", - "sinon-chai": "^3.3.0" - } -} diff --git a/packages/authentication-oauth2/test/express/error-handler.test.js b/packages/authentication-oauth2/test/express/error-handler.test.js deleted file mode 100644 index c7a9fe35ac..0000000000 --- a/packages/authentication-oauth2/test/express/error-handler.test.js +++ /dev/null @@ -1,54 +0,0 @@ -/* eslint-disable no-unused-expressions */ -const { expect } = require('chai'); - -const errorHandler = require('../../lib/express/error-handler'); - -describe('express:error-handler', () => { - let req; - let res; - let error; - let options; - - beforeEach(() => { - req = {}; - options = {}; - res = { - hook: { - data: { - __redirect: { - url: '/app' - } - } - } - }; - error = new Error('Authentication Error'); - }); - - describe('when failureRedirect is set', () => { - it('sets the redirect object on the response', done => { - options.failureRedirect = '/login'; - errorHandler(options)(error, req, res, () => { - expect(res.hook.data.__redirect).to.deep.equal({ status: 302, url: options.failureRedirect }); - done(); - }); - }); - - it('calls next with error', done => { - delete res.hook; - errorHandler(options)(error, req, res, e => { - expect(e).to.equal(error); - done(); - }); - }); - }); - - describe('when failureRedirect is not set', done => { - it('calls next with error', done => { - delete res.hook; - errorHandler(options)(error, req, res, e => { - expect(e).to.equal(error); - done(); - }); - }); - }); -}); diff --git a/packages/authentication-oauth2/test/express/handler.test.js b/packages/authentication-oauth2/test/express/handler.test.js deleted file mode 100644 index 665a25b563..0000000000 --- a/packages/authentication-oauth2/test/express/handler.test.js +++ /dev/null @@ -1,96 +0,0 @@ -/* eslint-disable no-unused-expressions */ -const chai = require('chai'); -const sinon = require('sinon'); -const sinonChai = require('sinon-chai'); -const handler = require('../../lib/express/handler'); - -const { expect } = chai; - -chai.use(sinonChai); - -describe('express:handler', () => { - let req; - let res; - let service; - let options; - let user; - let payload; - let accessToken = 'access'; - - beforeEach(() => { - payload = { userId: 1 }; - user = { name: 'Bob' }; - options = { entity: 'user', name: 'github' }; - service = { - create: sinon.stub().returns(Promise.resolve({ accessToken })) - }; - - req = { - user, - payload, - app: { - get: sinon.stub().returns({ path: '/authentication' }), - service: sinon.stub().returns(service) - } - }; - res = {}; - }); - - afterEach(() => { - req.app.service.reset(); - service.create.reset(); - }); - - it('calls create on the authentication service', done => { - const params = { - authenticated: true, - payload, - user - }; - - handler(options)(req, res, () => { - expect(req.app.service).to.have.been.calledOnce; - expect(req.app.service).to.have.been.calledWith('/authentication'); - expect(service.create).to.have.been.calledOnce; - expect(service.create).to.have.been.calledWith({ user, payload }, params); - done(); - }); - }); - - describe('when create succeeds', () => { - it('sets res.data', done => { - handler(options)(req, res, () => { - expect(res.data).to.deep.equal({ accessToken }); - done(); - }); - }); - - it('calls next', done => { - handler(options)(req, res, done); - }); - - describe('when successRedirect is set', () => { - it('sets the redirect object on the request', done => { - options.successRedirect = '/app'; - handler(options)(req, res, () => { - expect(res.hook.data.__redirect).to.deep.equal({ status: 302, url: options.successRedirect }); - done(); - }); - }); - }); - }); - - describe('when create fails', () => { - beforeEach(() => { - service.create = sinon.stub().returns(Promise.reject(new Error('Auth Error'))); - req.app.service = sinon.stub().returns(service); - }); - - it('calls next with an error', done => { - handler(options)(req, res, error => { - expect(error).to.not.equal(undefined); - done(); - }); - }); - }); -}); diff --git a/packages/authentication-oauth2/test/fixtures/strategy.js b/packages/authentication-oauth2/test/fixtures/strategy.js deleted file mode 100644 index 58142848d4..0000000000 --- a/packages/authentication-oauth2/test/fixtures/strategy.js +++ /dev/null @@ -1,41 +0,0 @@ -const passport = require('passport-strategy'); -const util = require('util'); - -function Strategy (options, verify) { - passport.Strategy.call(this); - this.name = 'mock'; - this._options = options; - this._verify = verify; -} - -util.inherits(Strategy, passport.Strategy); - -Strategy.prototype.authenticate = function (req, options) { - const accessToken = 'mockAccessToken'; - const refreshToken = 'mockRefreshToken'; - const profile = { name: 'Mocky Mockerson' }; - - const callback = function (error, user, info) { - if (error) { - return this.error(error); - } - - if (info && info.pass) { - return this.pass(); - } - - if (info && info.url) { - return this.redirect(info.url, info.status); - } - - if (!user) { - return this.fail(info.challenge, info.status); - } - - return this.success(user, info); - }.bind(this); - - this._verify(req, accessToken, refreshToken, profile, callback); -}; - -module.exports = Strategy; diff --git a/packages/authentication-oauth2/test/index.test.js b/packages/authentication-oauth2/test/index.test.js deleted file mode 100644 index 9343675973..0000000000 --- a/packages/authentication-oauth2/test/index.test.js +++ /dev/null @@ -1,291 +0,0 @@ -/* eslint-disable no-unused-expressions */ -const feathers = require('@feathersjs/feathers'); -const expressify = require('@feathersjs/express'); -const authentication = require('@feathersjs/authentication'); -const memory = require('feathers-memory'); -const chai = require('chai'); -const sinon = require('sinon'); -const sinonChai = require('sinon-chai'); - -const oauth2 = require('../lib'); -const Strategy = require('./fixtures/strategy'); - -const { Verifier } = oauth2; -const { expect } = chai; - -chai.use(sinonChai); - -describe('@feathersjs/authentication-oauth2', () => { - it('is CommonJS compatible', () => { - expect(typeof require('../lib')).to.equal('function'); - }); - - it('exports default', () => { - expect(oauth2.default).to.equal(oauth2); - }); - - it('basic functionality', () => { - expect(typeof oauth2).to.equal('function'); - }); - - it('exposes the Verifier class', () => { - expect(typeof Verifier).to.equal('function'); - expect(typeof oauth2.Verifier).to.equal('function'); - }); - - describe('initialization', () => { - let app; - let config; - let globalConfig; - - beforeEach(() => { - config = { - name: 'github', - Strategy, - clientID: '1234', - clientSecret: 'secret' - }; - - globalConfig = { - secret: 'supersecret', - github: { - clientID: '1234', - clientSecret: 'secret', - scope: ['user'] - } - }; - - app = expressify(feathers()); - app.set('host', 'localhost'); - app.set('port', 3030); - app.use('/users', memory()); - app.configure(authentication(globalConfig)); - }); - - it('throws an error if passport has not been registered', () => { - expect(() => { - expressify(feathers()).configure(oauth2()); - }).to.throw(); - }); - - it('throws an error if strategy name is missing', () => { - expect(() => { - delete config.name; - app.configure(oauth2(config)); - }).to.throw(); - }); - - it('throws an error if Strategy is missing', () => { - expect(() => { - delete config.Strategy; - app.configure(oauth2(config)); - }).to.throw(); - }); - - it('throws an error if clientID is missing', () => { - expect(() => { - delete config.clientID; - delete globalConfig.github.clientID; - expressify(feathers()).configure(authentication(globalConfig)).configure(oauth2(config)); - }).to.throw(); - }); - - it('throws an error if clientSecret is missing', () => { - expect(() => { - delete config.clientSecret; - delete globalConfig.github.clientSecret; - expressify(feathers()).configure(authentication(globalConfig)).configure(oauth2(config)); - }).to.throw(); - }); - - it('registers the oauth2 passport strategy', () => { - sinon.spy(app.passport, 'use'); - sinon.spy(config, 'Strategy'); - app.configure(oauth2(config)); - app.setup(); - - expect(config.Strategy).to.have.been.calledOnce; - expect(app.passport.use).to.have.been.calledWith(config.name); - - app.passport.use.restore(); - config.Strategy.restore(); - }); - - it('registers the strategy options', () => { - sinon.spy(app.passport, 'options'); - app.configure(oauth2(config)); - app.setup(); - - expect(app.passport.options).to.have.been.calledOnce; - - app.passport.options.restore(); - }); - - it('registers the redirect options on strategy options', () => { - sinon.spy(authentication.express, 'authenticate'); - - const mergedOptions = Object.assign({}, config, globalConfig); - app.configure(oauth2(mergedOptions)); - app.setup(); - - delete mergedOptions.Strategy; - expect(authentication.express.authenticate).to.have.been.calledWith(config.name, sinon.match(mergedOptions)); - - authentication.express.authenticate.restore(); - }); - - describe('passport strategy options', () => { - let authOptions; - let args; - - beforeEach(() => { - config.custom = true; - sinon.spy(config, 'Strategy'); - app.configure(oauth2(config)); - app.setup(); - authOptions = app.get('authentication'); - args = config.Strategy.getCall(0).args[0]; - }); - - afterEach(() => { - config.Strategy.restore(); - }); - - it('sets path', () => { - expect(args.path).to.equal(`/auth/${config.name}`); - }); - - it('sets callbackPath', () => { - expect(args.callbackPath).to.equal(`/auth/${config.name}/callback`); - }); - - it('sets callbackURL', () => { - expect(args.callbackURL).to.equal(`http://localhost:3030/auth/${config.name}/callback`); - }); - - it('sets idField', () => { - expect(args.idField).to.equal(`${config.name}Id`); - }); - - it('sets entity', () => { - expect(args.entity).to.equal(authOptions.entity); - }); - - it('sets service', () => { - expect(args.service).to.equal(authOptions.service); - }); - - it('sets session', () => { - expect(args.session).to.equal(authOptions.session); - }); - - it('sets passReqToCallback', () => { - expect(args.passReqToCallback).to.equal(authOptions.passReqToCallback); - }); - - it('supports setting custom options', () => { - expect(args.custom).to.equal(true); - }); - }); - - it('mixes in global config for strategy', () => { - delete config.clientID; - delete config.clientSecret; - sinon.spy(config, 'Strategy'); - - app.configure(oauth2(config)); - app.setup(); - - expect(config.Strategy.getCall(0).args[0].scope).to.deep.equal(['user']); - - config.Strategy.restore(); - }); - - it('supports overriding default options', () => { - sinon.spy(config, 'Strategy'); - config.entity = 'organization'; - app.configure(oauth2(config)); - app.setup(); - - expect(config.Strategy.getCall(0).args[0].entity).to.equal('organization'); - - config.Strategy.restore(); - }); - - it('registers express get route', () => { - sinon.spy(app, 'get'); - app.configure(oauth2(config)); - app.setup(); - - expect(app.get).to.have.been.calledWith(`/auth/${config.name}`); - - app.get.restore(); - }); - - it('registers express callback route', () => { - sinon.spy(app, 'get'); - app.configure(oauth2(config)); - app.setup(); - - expect(app.get).to.have.been.calledWith(`/auth/${config.name}/callback`); - - app.get.restore(); - }); - - it('registers custom express callback route', () => { - sinon.spy(app, 'get'); - config.callbackPath = `/v1/api/auth/${config.name}/callback`; - app.configure(oauth2(config)); - app.setup(); - - expect(app.get).to.have.been.calledWith(config.callbackPath); - - app.get.restore(); - }); - - describe('custom Verifier', () => { - it('throws an error if a verify function is missing', () => { - expect(() => { - class CustomVerifier { - constructor (app) { - this.app = app; - } - } - config.Verifier = CustomVerifier; - app.configure(oauth2(config)); - app.setup(); - }).to.throw(); - }); - - it('verifies through custom verify function', () => { - const User = { - email: 'admin@feathersjs.com', - password: 'password' - }; - - const req = { - query: {}, - body: Object.assign({}, User), - headers: {}, - cookies: {} - }; - class CustomVerifier extends Verifier { - verify (req, accessToken, refreshToken, profile, done) { - expect(accessToken).to.equal('mockAccessToken'); - expect(refreshToken).to.equal('mockRefreshToken'); - expect(profile).to.deep.equal({ name: 'Mocky Mockerson' }); - done(null, User); - } - } - - config.Verifier = CustomVerifier; - app.configure(oauth2(config)); - app.setup(); - - return app.authenticate('github')(req).then(result => { - expect(result.data.user).to.deep.equal(User); - }); - }); - }); - }); -}); diff --git a/packages/authentication-oauth2/test/verifier.test.js b/packages/authentication-oauth2/test/verifier.test.js deleted file mode 100644 index 102ede1ba7..0000000000 --- a/packages/authentication-oauth2/test/verifier.test.js +++ /dev/null @@ -1,331 +0,0 @@ -/* eslint-disable no-unused-expressions */ -const feathers = require('@feathersjs/feathers'); -const expressify = require('@feathersjs/express'); -const authentication = require('@feathersjs/authentication'); - -const { Verifier } = require('../lib'); - -const chai = require('chai'); -const sinon = require('sinon'); -const sinonChai = require('sinon-chai'); - -const { expect } = chai; - -chai.use(sinonChai); - -describe('Verifier', () => { - let service; - let app; - let options; - let verifier; - let user; - - beforeEach(() => { - user = { id: 1, email: 'admin@feathersjs.com' }; - service = { - id: 'id', - find: sinon.stub().returns(Promise.resolve([user])), - create: sinon.stub().returns(Promise.resolve(user)), - patch: sinon.stub().returns(Promise.resolve(user)) - }; - - app = expressify(feathers()); - app.use('users', service) - .configure(authentication({ secret: 'supersecret' })); - - options = app.get('authentication'); - options.name = 'github'; - options.idField = 'githubId'; - - verifier = new Verifier(app, options); - }); - - it('is CommonJS compatible', () => { - expect(typeof require('../lib/verifier')).to.equal('function'); - }); - - it('exposes the Verifier class', () => { - expect(typeof Verifier).to.equal('function'); - }); - - describe('constructor', () => { - it('retains an app reference', () => { - expect(verifier.app).to.deep.equal(app); - }); - - it('sets options', () => { - expect(verifier.options).to.deep.equal(options); - }); - - it('sets service using service path', () => { - expect(verifier.service).to.deep.equal(app.service('users')); - }); - - it('sets a passed in service instance', () => { - options.service = service; - expect(new Verifier(app, options).service).to.deep.equal(service); - }); - - describe('when service is undefined', () => { - it('throws an error', () => { - expect(() => { - new Verifier(app, {}); // eslint-disable-line - }).to.throw(); - }); - }); - }); - - describe('_updateEntity', () => { - let entity; - let data; - let args; - - beforeEach(() => { - entity = { id: 1, name: 'Admin' }; - data = { - accessToken: 'access', - refreshToken: 'refresh', - profile: { - id: 1234, - name: 'Admin' - } - }; - return verifier._updateEntity(entity, data).then(() => { - args = service.patch.getCall(0).args; - }); - }); - - it('calls patch on passed in service', () => { - expect(service.patch).to.have.been.calledOnce; - }); - - it('passes id', () => { - expect(args[0]).to.equal(entity.id); - }); - - it('passes patch data', () => { - expect(args[1].githubId).to.equal(data.profile.id); - expect(args[1].github).to.deep.equal(data); - }); - - it('passes oauth provider via params', () => { - expect(args[2]).to.deep.equal({ oauth: { provider: 'github' } }); - }); - }); - - describe('_createEntity', () => { - let data; - let args; - - beforeEach(() => { - data = { - accessToken: 'access', - refreshToken: 'refresh', - profile: { - id: 1234, - name: 'Admin' - } - }; - return verifier._createEntity(data).then(() => { - args = service.create.getCall(0).args; - }); - }); - - it('calls create on passed in service', () => { - expect(service.create).to.have.been.calledOnce; - }); - - it('passes entity', () => { - expect(args[0].githubId).to.equal(data.profile.id); - expect(args[0].github).to.deep.equal(data); - }); - - it('passes oauth provider via params', () => { - expect(args[1]).to.deep.equal({ oauth: { provider: 'github' } }); - }); - }); - - describe('_normalizeResult', () => { - describe('when has results', () => { - it('returns entity when paginated', () => { - return verifier._normalizeResult({ data: [user] }).then(result => { - expect(result).to.deep.equal(user); - }); - }); - - it('returns entity when not paginated', () => { - return verifier._normalizeResult([user]).then(result => { - expect(result).to.deep.equal(user); - }); - }); - - it('calls toObject on entity when present', () => { - user.toObject = sinon.spy(); - return verifier._normalizeResult({ data: [user] }).then(() => { - expect(user.toObject).to.have.been.calledOnce; - }); - }); - - it('calls toJSON on entity when present', () => { - user.toJSON = sinon.spy(); - return verifier._normalizeResult({ data: [user] }).then(() => { - expect(user.toJSON).to.have.been.calledOnce; - }); - }); - }); - - describe('when no results', () => { - it('rejects with false when paginated', () => { - return verifier._normalizeResult({ data: [] }).catch(error => { - expect(error).to.equal(false); - }); - }); - - it('rejects with false when not paginated', () => { - return verifier._normalizeResult([]).catch(error => { - expect(error).to.equal(false); - }); - }); - }); - }); - - describe('verify', () => { - it('calls find on the provided service', done => { - verifier.verify({}, 'access', 'refresh', { id: 1234 }, () => { - const query = { githubId: 1234, $limit: 1 }; - expect(service.find).to.have.been.calledOnce; - expect(service.find).to.have.been.calledWith({ query }); - done(); - }); - }); - - it('calls with query from makeQuery', done => { - options = Object.assign({}, options, { makeQuery: sinon.stub().returns({ key: 'value' }) }); - verifier = new Verifier(app, options); - verifier.verify({}, 'access', 'refresh', { id: 1234 }, () => { - const query = { key: 'value', $limit: 1 }; - expect(options.makeQuery).to.have.been.calledOnce; - expect(service.find).to.have.been.calledWith({ query }); - done(); - }); - }); - - it('calls _normalizeResult', done => { - sinon.spy(verifier, '_normalizeResult'); - verifier.verify({}, 'access', 'refresh', { id: 1234 }, () => { - expect(verifier._normalizeResult).to.have.been.calledOnce; - verifier._normalizeResult.restore(); - done(); - }); - }); - - describe('when entity exists on request object', () => { - it('calls _updateEntity', done => { - sinon.spy(verifier, '_updateEntity'); - const req = { 'user': { name: 'Admin' } }; - verifier.verify(req, 'access', 'refresh', { id: 1234 }, () => { - expect(verifier._updateEntity).to.have.been.calledOnce; - verifier._updateEntity.restore(); - done(); - }); - }); - }); - - describe('when entity exists on request.params object', () => { - it('calls _updateEntity', done => { - sinon.spy(verifier, '_updateEntity'); - const req = { - params: { - 'user': { name: 'Admin' } - } - }; - verifier.verify(req, 'access', 'refresh', { id: 1234 }, () => { - expect(verifier._updateEntity).to.have.been.calledOnce; - verifier._updateEntity.restore(); - done(); - }); - }); - }); - - it('calls _createEntity when entity not found', done => { - sinon.spy(verifier, '_createEntity'); - verifier._normalizeResult = () => Promise.resolve(null); - verifier.verify({}, 'access', 'refresh', { id: 1234 }, () => { - expect(verifier._createEntity).to.have.been.calledOnce; - verifier._createEntity.restore(); - done(); - }); - }); - - it('calls _updateEntity when entity is found', done => { - sinon.spy(verifier, '_updateEntity'); - verifier.verify({}, 'access', 'refresh', { id: 1234 }, () => { - expect(verifier._updateEntity).to.have.been.calledOnce; - verifier._updateEntity.restore(); - done(); - }); - }); - - it('returns the entity', done => { - verifier.verify({}, 'access', 'refresh', { id: 1234 }, (error, entity) => { - expect(error).to.equal(null); - expect(entity).to.deep.equal(user); - done(); - }); - }); - - it('handles false rejections in promise chain', done => { - verifier._updateEntity = () => Promise.reject(false); // eslint-disable-line - verifier.verify({}, 'access', 'refresh', { id: 1234 }, (error, entity) => { - expect(error).to.equal(null); - expect(entity).to.equal(false); - done(); - }); - }); - - it('returns errors', done => { - const authError = new Error('An error'); - verifier._normalizeResult = () => Promise.reject(authError); - verifier.verify({}, 'access', 'refresh', { id: 1234 }, (error, entity) => { - expect(error).to.equal(authError); - expect(entity).to.equal(undefined); - done(); - }); - }); - }); -}); - -describe('Verifier without service.id', function () { - let service; - let app; - let options; - let verifier; - let user; - - beforeEach(() => { - user = { id: 1, email: 'admin@feathersjs.com' }; - service = { - find: sinon.stub().returns(Promise.resolve([user])), - create: sinon.stub().returns(Promise.resolve(user)), - patch: sinon.stub().returns(Promise.resolve(user)) - }; - - app = expressify(feathers()); - app.use('users', service) - .configure(authentication({ secret: 'supersecret' })); - - options = app.get('authentication'); - options.name = 'github'; - options.idField = 'githubId'; - - verifier = new Verifier(app, options); - }); - - it('throws an error when service.id is not set', done => { - verifier.verify({}, 'access', 'refresh', { id: 1234 }, (error, entity) => { - expect(error.message.includes('the `id` property must be set')).to.equal(true); - expect(entity).to.equal(undefined); - done(); - }); - }); -}); diff --git a/packages/authentication/lib/core.js b/packages/authentication/lib/core.js index 4d7fc0c544..26e39a1324 100644 --- a/packages/authentication/lib/core.js +++ b/packages/authentication/lib/core.js @@ -20,12 +20,12 @@ module.exports = class AuthenticationBase { this.configKey = configKey; app.set('defaultAuthentication', app.get('defaultAuthentication') || configKey); - app.set(configKey, getOptions(options, app.get(configKey))); + app.set(configKey, merge({}, app.get(configKey), options)); } get configuration () { // Always returns a copy of the authentication configuration - return merge({}, this.app.get(this.configKey)); + return getOptions(this.app.get(this.configKey)); } get strategyNames () { @@ -46,6 +46,10 @@ module.exports = class AuthenticationBase { strategy.setAuthentication(this); } + if (typeof strategy.verifyConfiguration === 'function') { + strategy.verifyConfiguration(); + } + // Register strategy as name this.strategies[name] = strategy; } diff --git a/packages/authentication/lib/index.js b/packages/authentication/lib/index.js index a060ab2f71..5c34cd5db3 100644 --- a/packages/authentication/lib/index.js +++ b/packages/authentication/lib/index.js @@ -1,10 +1,8 @@ const AuthenticationCore = require('./core'); const AuthenticationService = require('./service'); -const BaseStrategy = require('./strategy'); const JWTStrategy = require('./jwt'); const hooks = require('./hooks'); -exports.BaseStrategy = BaseStrategy; exports.JWTStrategy = JWTStrategy; exports.AuthenticationCore = AuthenticationCore; exports.AuthenticationService = AuthenticationService; diff --git a/packages/authentication/lib/jwt.js b/packages/authentication/lib/jwt.js index 426b6e1931..d7d92aa5e0 100644 --- a/packages/authentication/lib/jwt.js +++ b/packages/authentication/lib/jwt.js @@ -1,10 +1,33 @@ const { NotAuthenticated } = require('@feathersjs/errors'); -const BaseStrategy = require('./strategy'); const SPLIT_HEADER = /(\S+)\s+(\S+)/; -module.exports = class JWTStrategy extends BaseStrategy { +module.exports = class JWTStrategy { + setAuthentication (auth) { + this.authentication = auth; + } + + setApplication (app) { + this.app = app; + } + + setName (name) { + this.name = name; + } + + get configuration () { + const authConfig = this.authentication.configuration; + const config = authConfig[this.name]; + + return Object.assign({ + entity: authConfig.entity, + service: authConfig.service, + header: 'Authorization', + schemes: [ 'Bearer', 'JWT' ] + }, config); + } + getEntity (id, params) { - const { service } = this.authentication.configuration; + const { service } = this.configuration; const entityService = this.app.service(service); if (!entityService) { @@ -18,7 +41,7 @@ module.exports = class JWTStrategy extends BaseStrategy { authenticate (authentication, params) { const { accessToken, strategy } = authentication; - const { entity } = this.authentication.configuration; + const { entity } = this.configuration; if (!accessToken || (strategy && strategy !== this.name)) { return Promise.reject(new NotAuthenticated('Not authenticated')); @@ -59,6 +82,10 @@ module.exports = class JWTStrategy extends BaseStrategy { current => new RegExp(current, 'i').test(scheme) ); + if (scheme && !hasScheme) { + return null; + } + return Object.assign(result, { accessToken: hasScheme ? schemeValue : headerValue }); diff --git a/packages/authentication/lib/options.js b/packages/authentication/lib/options.js index a3f6cad566..1c0b8afcc7 100644 --- a/packages/authentication/lib/options.js +++ b/packages/authentication/lib/options.js @@ -3,17 +3,13 @@ const { merge } = require('lodash'); const defaults = { entity: 'user', service: 'users', - allowStrategies: [], + strategies: [], jwtOptions: { header: { typ: 'access' }, // by default is an access token but can be any type audience: 'https://yourdomain.com', // The resource server where the token is processed issuer: 'feathers', // The issuing server, application or resource algorithm: 'HS256', expiresIn: '1d' - }, - jwt: { - header: 'authorization', - schemes: [ 'Bearer', 'JWT' ] } }; diff --git a/packages/authentication/lib/strategy.js b/packages/authentication/lib/strategy.js deleted file mode 100644 index 56027a3495..0000000000 --- a/packages/authentication/lib/strategy.js +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = class BaseStrategy { - setAuthentication (auth) { - this.authentication = auth; - } - - setApplication (app) { - this.app = app; - } - - setName (name) { - this.name = name; - } - - get configuration () { - return this.authentication.configuration[this.name]; - } -}; diff --git a/packages/authentication/test/core.test.js b/packages/authentication/test/core.test.js index d809916576..8e836396e2 100644 --- a/packages/authentication/test/core.test.js +++ b/packages/authentication/test/core.test.js @@ -41,15 +41,15 @@ describe('authentication/core', () => { it('sets configKey and defaultAuthentication', () => { assert.strictEqual(app.get('defaultAuthentication'), 'authentication'); - assert.deepStrictEqual(app.get('authentication'), auth.configuration); }); it('uses default configKey', () => { const otherApp = feathers(); const otherAuth = new AuthenticationCore(otherApp); + assert.ok(otherAuth); assert.strictEqual(otherApp.get('defaultAuthentication'), 'authentication'); - assert.deepStrictEqual(otherApp.get('authentication'), otherAuth.configuration); + assert.deepStrictEqual(otherApp.get('authentication'), {}); }); }); diff --git a/packages/authentication/test/jwt.test.js b/packages/authentication/test/jwt.test.js index 3d38fc6cc2..99bf0d8114 100644 --- a/packages/authentication/test/jwt.test.js +++ b/packages/authentication/test/jwt.test.js @@ -185,5 +185,15 @@ describe('authentication/jwt', () => { }); }); }); + + it('return null when scheme does not match', () => { + return app.service('authentication').parse({ + headers: { + authorization: ` Basic something` + } + }, {}, 'jwt').then(result => { + assert.strictEqual(result, null); + }); + }); }); });