From f3ce1bb80c8fa16733cd4b90ecd4f38611bbfaf4 Mon Sep 17 00:00:00 2001 From: Thom Seddon Date: Thu, 18 Apr 2019 23:18:50 +0100 Subject: [PATCH 01/30] Seeking maintainers --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 9cf373748..e67c00666 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ +# This project is seeking maintainer(s) + +## This is a popular project in need of maintenance, please contact @thomseddon on GitHub if you are interested in contributing to this project. + # oauth2-server [![npm Version][npm-image]][npm-url] From 0bff971ea38977174fbe5ec6b96a954dcd1a0c9c Mon Sep 17 00:00:00 2001 From: Ankit Date: Sun, 21 Apr 2019 15:40:44 +0530 Subject: [PATCH 02/30] rewrite-in-typescript --- .editorconfig | 14 + .gitignore | 25 +- .jshintignore | 1 - .jshintrc | 26 - .npmignore | 1 - .prettierrc | 5 + .travis.yml | 9 +- CHANGELOG.md | 3 + LICENSE | 0 README.md | 6 +- docs/Makefile | 0 docs/_static/custom.css | 0 docs/_static/favicon.ico | Bin docs/api/errors/access-denied-error.rst | 0 docs/api/errors/index.rst | 0 docs/api/errors/insufficient-scope-error.rst | 0 docs/api/errors/invalid-argument-error.rst | 0 docs/api/errors/invalid-client-error.rst | 0 docs/api/errors/invalid-grant-error.rst | 0 docs/api/errors/invalid-request-error.rst | 0 docs/api/errors/invalid-scope-error.rst | 0 docs/api/errors/invalid-token-error.rst | 0 docs/api/errors/oauth-error.rst | 0 docs/api/errors/server-error.rst | 0 docs/api/errors/unauthorized-client-error.rst | 0 .../api/errors/unauthorized-request-error.rst | 0 .../errors/unsupported-grant-type-error.rst | 0 .../unsupported-response-type-error.rst | 0 docs/api/oauth2-server.rst | 0 docs/api/request.rst | 0 docs/api/response.rst | 0 docs/conf.py | 0 docs/docs/adapters.rst | 0 docs/docs/getting-started.rst | 0 docs/index.rst | 0 docs/make.bat | 0 docs/misc/extension-grants.rst | 0 docs/misc/migrating-v2-to-v3.rst | 6 +- docs/model/overview.rst | 0 docs/model/spec.rst | 0 docs/npm_conf.py | 0 index.js | 35 - index.ts | 2 + lib/errors/access-denied-error.js | 38 - lib/errors/access-denied-error.ts | 15 + lib/errors/insufficient-scope-error.js | 38 - lib/errors/insufficient-scope-error.ts | 15 + lib/errors/invalid-argument-error.js | 34 - lib/errors/invalid-argument-error.ts | 7 + lib/errors/invalid-client-error.js | 39 - lib/errors/invalid-client-error.ts | 16 + lib/errors/invalid-grant-error.js | 40 - lib/errors/invalid-grant-error.ts | 17 + lib/errors/invalid-request-error.js | 39 - lib/errors/invalid-request-error.ts | 16 + lib/errors/invalid-scope-error.js | 38 - lib/errors/invalid-scope-error.ts | 15 + lib/errors/invalid-token-error.js | 38 - lib/errors/invalid-token-error.ts | 15 + lib/errors/oauth-error.js | 45 - lib/errors/oauth-error.ts | 35 + lib/errors/server-error.js | 38 - lib/errors/server-error.ts | 15 + lib/errors/unauthorized-client-error.js | 38 - lib/errors/unauthorized-client-error.ts | 15 + lib/errors/unauthorized-request-error.js | 41 - lib/errors/unauthorized-request-error.ts | 18 + lib/errors/unsupported-grant-type-error.js | 38 - lib/errors/unsupported-grant-type-error.ts | 19 + lib/errors/unsupported-response-type-error.js | 39 - lib/errors/unsupported-response-type-error.ts | 20 + lib/grant-types/abstract-grant-type.js | 123 -- lib/grant-types/abstract-grant-type.ts | 112 ++ .../authorization-code-grant-type.js | 206 -- .../authorization-code-grant-type.ts | 212 +++ .../client-credentials-grant-type.js | 111 -- .../client-credentials-grant-type.ts | 88 + lib/grant-types/password-grant-type.js | 133 -- lib/grant-types/password-grant-type.ts | 118 ++ lib/grant-types/refresh-token-grant-type.js | 180 -- lib/grant-types/refresh-token-grant-type.ts | 167 ++ lib/handlers/authenticate-handler.js | 263 --- lib/handlers/authenticate-handler.ts | 268 +++ lib/handlers/authorize-handler.js | 332 ---- lib/handlers/authorize-handler.ts | 369 ++++ lib/handlers/token-handler.js | 297 --- lib/handlers/token-handler.ts | 323 ++++ .../authorization-code.interface.ts | 15 + lib/interfaces/client.interface.ts | 11 + lib/interfaces/model.interface.ts | 189 ++ lib/interfaces/refresh-token.interface.ts | 14 + lib/interfaces/token.interface.ts | 16 + lib/interfaces/user.interface.ts | 7 + lib/models/token-model.js | 65 - lib/models/token-model.ts | 81 + lib/request.js | 73 - lib/request.ts | 64 + lib/response-types/code-response-type.js | 43 - lib/response-types/code-response-type.ts | 29 + lib/response-types/token-response-type.js | 21 - lib/response-types/token-response-type.ts | 8 + lib/response.js | 58 - lib/response.ts | 49 + lib/server.js | 83 - lib/server.ts | 110 ++ lib/token-types/bearer-token-type.js | 62 - lib/token-types/bearer-token-type.ts | 60 + lib/token-types/mac-token-type.js | 21 - lib/token-types/mac-token-type.ts | 8 + lib/types/falsy.type.ts | 1 + lib/utils/token-util.js | 29 - lib/utils/token-util.ts | 16 + lib/validator/is.js | 81 - lib/validator/is.ts | 64 + package-lock.json | 1458 ++++++++++++++ package.json | 77 +- test/.mocharc.jsonc | 7 + test/assertions.js | 21 +- .../grant-types/abstract-grant-type.spec.ts | 231 +++ .../grant-types/abstract-grant-type_test.js | 174 -- .../authorization-code-grant-type.spec.ts | 1015 ++++++++++ .../authorization-code-grant-type_test.js | 594 ------ .../client-credentials-grant-type.spec.ts | 381 ++++ .../client-credentials-grant-type_test.js | 256 --- .../grant-types/password-grant-type.spec.ts | 502 +++++ .../grant-types/password-grant-type_test.js | 344 ---- .../refresh-token-grant-type.spec.ts | 840 +++++++++ .../refresh-token-grant-type_test.js | 536 ------ .../handlers/authenticate-handler.spec.ts | 692 +++++++ .../handlers/authenticate-handler_test.js | 563 ------ .../handlers/authorize-handler.spec.ts | 1570 +++++++++++++++ .../handlers/authorize-handler_test.js | 1126 ----------- .../handlers/token-handler.spec.ts | 1676 +++++++++++++++++ .../handlers/token-handler_test.js | 1079 ----------- test/integration/request.spec.ts | 183 ++ test/integration/request_test.js | 159 -- .../response-types/code-response-type.spec.ts | 64 + .../response-types/code-response-type_test.js | 64 - test/integration/response.spec.ts | 75 + test/integration/response_test.js | 75 - test/integration/server.spec.ts | 351 ++++ test/integration/server_test.js | 238 --- .../token-types/bearer-token-type.spec.ts | 135 ++ .../token-types/bearer-token-type_test.js | 93 - test/integration/utils/token-util.spec.ts | 19 + test/integration/utils/token-util_test.js | 24 - test/mocha.opts | 4 - test/tslint.json | 29 + .../grant-types/abstract-grant-type.spec.ts | 53 + .../grant-types/abstract-grant-type_test.js | 47 - .../authorization-code-grant-type.spec.ts | 121 ++ .../authorization-code-grant-type_test.js | 90 - .../client-credentials-grant-type.spec.ts | 66 + .../client-credentials-grant-type_test.js | 62 - .../grant-types/password-grant-type.spec.ts | 78 + .../grant-types/password-grant-type_test.js | 66 - .../refresh-token-grant-type.spec.ts | 298 +++ .../refresh-token-grant-type_test.js | 200 -- .../handlers/authenticate-handler.spec.ts | 166 ++ .../handlers/authenticate-handler_test.js | 153 -- test/unit/handlers/authorize-handler.spec.ts | 139 ++ test/unit/handlers/authorize-handler_test.js | 102 - test/unit/handlers/token-handler.spec.ts | 45 + test/unit/handlers/token-handler_test.js | 37 - test/unit/models/token-model.spec.ts | 25 + test/unit/models/token-model_test.js | 25 - test/unit/request.spec.ts | 170 ++ test/unit/request_test.js | 168 -- test/unit/response.spec.ts | 114 ++ test/unit/response_test.js | 120 -- test/unit/server.spec.ts | 89 + test/unit/server_test.js | 90 - tsconfig.build.json | 4 + tsconfig.json | 17 + tslint.json | 26 + 175 files changed, 13390 insertions(+), 9330 deletions(-) create mode 100644 .editorconfig mode change 100644 => 100755 .gitignore delete mode 100644 .jshintignore delete mode 100644 .jshintrc delete mode 100644 .npmignore create mode 100755 .prettierrc mode change 100644 => 100755 .travis.yml mode change 100644 => 100755 CHANGELOG.md mode change 100644 => 100755 LICENSE mode change 100644 => 100755 README.md mode change 100644 => 100755 docs/Makefile mode change 100644 => 100755 docs/_static/custom.css mode change 100644 => 100755 docs/_static/favicon.ico mode change 100644 => 100755 docs/api/errors/access-denied-error.rst mode change 100644 => 100755 docs/api/errors/index.rst mode change 100644 => 100755 docs/api/errors/insufficient-scope-error.rst mode change 100644 => 100755 docs/api/errors/invalid-argument-error.rst mode change 100644 => 100755 docs/api/errors/invalid-client-error.rst mode change 100644 => 100755 docs/api/errors/invalid-grant-error.rst mode change 100644 => 100755 docs/api/errors/invalid-request-error.rst mode change 100644 => 100755 docs/api/errors/invalid-scope-error.rst mode change 100644 => 100755 docs/api/errors/invalid-token-error.rst mode change 100644 => 100755 docs/api/errors/oauth-error.rst mode change 100644 => 100755 docs/api/errors/server-error.rst mode change 100644 => 100755 docs/api/errors/unauthorized-client-error.rst mode change 100644 => 100755 docs/api/errors/unauthorized-request-error.rst mode change 100644 => 100755 docs/api/errors/unsupported-grant-type-error.rst mode change 100644 => 100755 docs/api/errors/unsupported-response-type-error.rst mode change 100644 => 100755 docs/api/oauth2-server.rst mode change 100644 => 100755 docs/api/request.rst mode change 100644 => 100755 docs/api/response.rst mode change 100644 => 100755 docs/conf.py mode change 100644 => 100755 docs/docs/adapters.rst mode change 100644 => 100755 docs/docs/getting-started.rst mode change 100644 => 100755 docs/index.rst mode change 100644 => 100755 docs/make.bat mode change 100644 => 100755 docs/misc/extension-grants.rst mode change 100644 => 100755 docs/misc/migrating-v2-to-v3.rst mode change 100644 => 100755 docs/model/overview.rst mode change 100644 => 100755 docs/model/spec.rst mode change 100644 => 100755 docs/npm_conf.py delete mode 100644 index.js create mode 100755 index.ts delete mode 100644 lib/errors/access-denied-error.js create mode 100755 lib/errors/access-denied-error.ts delete mode 100644 lib/errors/insufficient-scope-error.js create mode 100755 lib/errors/insufficient-scope-error.ts delete mode 100644 lib/errors/invalid-argument-error.js create mode 100755 lib/errors/invalid-argument-error.ts delete mode 100644 lib/errors/invalid-client-error.js create mode 100755 lib/errors/invalid-client-error.ts delete mode 100644 lib/errors/invalid-grant-error.js create mode 100755 lib/errors/invalid-grant-error.ts delete mode 100644 lib/errors/invalid-request-error.js create mode 100755 lib/errors/invalid-request-error.ts delete mode 100644 lib/errors/invalid-scope-error.js create mode 100755 lib/errors/invalid-scope-error.ts delete mode 100644 lib/errors/invalid-token-error.js create mode 100755 lib/errors/invalid-token-error.ts delete mode 100644 lib/errors/oauth-error.js create mode 100755 lib/errors/oauth-error.ts delete mode 100644 lib/errors/server-error.js create mode 100755 lib/errors/server-error.ts delete mode 100644 lib/errors/unauthorized-client-error.js create mode 100755 lib/errors/unauthorized-client-error.ts delete mode 100644 lib/errors/unauthorized-request-error.js create mode 100755 lib/errors/unauthorized-request-error.ts delete mode 100644 lib/errors/unsupported-grant-type-error.js create mode 100755 lib/errors/unsupported-grant-type-error.ts delete mode 100644 lib/errors/unsupported-response-type-error.js create mode 100755 lib/errors/unsupported-response-type-error.ts delete mode 100644 lib/grant-types/abstract-grant-type.js create mode 100755 lib/grant-types/abstract-grant-type.ts delete mode 100644 lib/grant-types/authorization-code-grant-type.js create mode 100755 lib/grant-types/authorization-code-grant-type.ts delete mode 100644 lib/grant-types/client-credentials-grant-type.js create mode 100755 lib/grant-types/client-credentials-grant-type.ts delete mode 100644 lib/grant-types/password-grant-type.js create mode 100755 lib/grant-types/password-grant-type.ts delete mode 100644 lib/grant-types/refresh-token-grant-type.js create mode 100755 lib/grant-types/refresh-token-grant-type.ts delete mode 100644 lib/handlers/authenticate-handler.js create mode 100755 lib/handlers/authenticate-handler.ts delete mode 100644 lib/handlers/authorize-handler.js create mode 100755 lib/handlers/authorize-handler.ts delete mode 100644 lib/handlers/token-handler.js create mode 100755 lib/handlers/token-handler.ts create mode 100644 lib/interfaces/authorization-code.interface.ts create mode 100644 lib/interfaces/client.interface.ts create mode 100644 lib/interfaces/model.interface.ts create mode 100644 lib/interfaces/refresh-token.interface.ts create mode 100644 lib/interfaces/token.interface.ts create mode 100644 lib/interfaces/user.interface.ts delete mode 100644 lib/models/token-model.js create mode 100755 lib/models/token-model.ts delete mode 100644 lib/request.js create mode 100755 lib/request.ts delete mode 100644 lib/response-types/code-response-type.js create mode 100755 lib/response-types/code-response-type.ts delete mode 100644 lib/response-types/token-response-type.js create mode 100755 lib/response-types/token-response-type.ts delete mode 100644 lib/response.js create mode 100755 lib/response.ts delete mode 100644 lib/server.js create mode 100755 lib/server.ts delete mode 100644 lib/token-types/bearer-token-type.js create mode 100755 lib/token-types/bearer-token-type.ts delete mode 100644 lib/token-types/mac-token-type.js create mode 100755 lib/token-types/mac-token-type.ts create mode 100644 lib/types/falsy.type.ts delete mode 100644 lib/utils/token-util.js create mode 100755 lib/utils/token-util.ts delete mode 100644 lib/validator/is.js create mode 100755 lib/validator/is.ts create mode 100755 package-lock.json mode change 100644 => 100755 package.json create mode 100644 test/.mocharc.jsonc mode change 100644 => 100755 test/assertions.js create mode 100755 test/integration/grant-types/abstract-grant-type.spec.ts delete mode 100644 test/integration/grant-types/abstract-grant-type_test.js create mode 100755 test/integration/grant-types/authorization-code-grant-type.spec.ts delete mode 100644 test/integration/grant-types/authorization-code-grant-type_test.js create mode 100755 test/integration/grant-types/client-credentials-grant-type.spec.ts delete mode 100644 test/integration/grant-types/client-credentials-grant-type_test.js create mode 100755 test/integration/grant-types/password-grant-type.spec.ts delete mode 100644 test/integration/grant-types/password-grant-type_test.js create mode 100755 test/integration/grant-types/refresh-token-grant-type.spec.ts delete mode 100644 test/integration/grant-types/refresh-token-grant-type_test.js create mode 100755 test/integration/handlers/authenticate-handler.spec.ts delete mode 100644 test/integration/handlers/authenticate-handler_test.js create mode 100755 test/integration/handlers/authorize-handler.spec.ts delete mode 100644 test/integration/handlers/authorize-handler_test.js create mode 100755 test/integration/handlers/token-handler.spec.ts delete mode 100644 test/integration/handlers/token-handler_test.js create mode 100755 test/integration/request.spec.ts delete mode 100644 test/integration/request_test.js create mode 100755 test/integration/response-types/code-response-type.spec.ts delete mode 100644 test/integration/response-types/code-response-type_test.js create mode 100755 test/integration/response.spec.ts delete mode 100644 test/integration/response_test.js create mode 100755 test/integration/server.spec.ts delete mode 100644 test/integration/server_test.js create mode 100755 test/integration/token-types/bearer-token-type.spec.ts delete mode 100644 test/integration/token-types/bearer-token-type_test.js create mode 100755 test/integration/utils/token-util.spec.ts delete mode 100644 test/integration/utils/token-util_test.js delete mode 100644 test/mocha.opts create mode 100755 test/tslint.json create mode 100755 test/unit/grant-types/abstract-grant-type.spec.ts delete mode 100644 test/unit/grant-types/abstract-grant-type_test.js create mode 100755 test/unit/grant-types/authorization-code-grant-type.spec.ts delete mode 100644 test/unit/grant-types/authorization-code-grant-type_test.js create mode 100755 test/unit/grant-types/client-credentials-grant-type.spec.ts delete mode 100644 test/unit/grant-types/client-credentials-grant-type_test.js create mode 100755 test/unit/grant-types/password-grant-type.spec.ts delete mode 100644 test/unit/grant-types/password-grant-type_test.js create mode 100755 test/unit/grant-types/refresh-token-grant-type.spec.ts delete mode 100644 test/unit/grant-types/refresh-token-grant-type_test.js create mode 100755 test/unit/handlers/authenticate-handler.spec.ts delete mode 100644 test/unit/handlers/authenticate-handler_test.js create mode 100755 test/unit/handlers/authorize-handler.spec.ts delete mode 100644 test/unit/handlers/authorize-handler_test.js create mode 100755 test/unit/handlers/token-handler.spec.ts delete mode 100644 test/unit/handlers/token-handler_test.js create mode 100755 test/unit/models/token-model.spec.ts delete mode 100644 test/unit/models/token-model_test.js create mode 100755 test/unit/request.spec.ts delete mode 100644 test/unit/request_test.js create mode 100755 test/unit/response.spec.ts delete mode 100644 test/unit/response_test.js create mode 100755 test/unit/server.spec.ts delete mode 100644 test/unit/server_test.js create mode 100755 tsconfig.build.json create mode 100755 tsconfig.json create mode 100755 tslint.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..7a7bb1570 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +# Editor configuration, see http://editorconfig.org +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index 820d105cc..8cac61aeb --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,22 @@ -node_modules/ -docs/_build/ -__pycache__/ -*.pyc + +# IDE +/.idea +/.awcache +/.vscode + +# misc +npm-debug.log + +# example +/quick-start + +# tests +# /test + +# dist +/dist +/node_modules + +.DS_Store +# conf diff --git a/.jshintignore b/.jshintignore deleted file mode 100644 index 3c3629e64..000000000 --- a/.jshintignore +++ /dev/null @@ -1 +0,0 @@ -node_modules diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 441e9934f..000000000 --- a/.jshintrc +++ /dev/null @@ -1,26 +0,0 @@ -{ - "bitwise": true, - "curly": true, - "eqeqeq": true, - "esnext": true, - "expr": true, - "globalstrict": false, - "immed": true, - "indent": 2, - "jquery": true, - "latedef": false, - "mocha": true, - "newcap": true, - "noarg": true, - "node": true, - "noyield": true, - "predef": ["-Promise"], - "quotmark": "single", - "regexp": true, - "smarttabs": true, - "strict": false, - "trailing": false, - "undef": true, - "unused": true, - "white": false -} diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 65e3ba2ed..000000000 --- a/.npmignore +++ /dev/null @@ -1 +0,0 @@ -test/ diff --git a/.prettierrc b/.prettierrc new file mode 100755 index 000000000..6de9cff5b --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "singleQuote": true, + "trailingComma": "all", + "endOfLine": "lf" +} diff --git a/.travis.yml b/.travis.yml old mode 100644 new mode 100755 index 1ba9eefaa..45f1da500 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,10 @@ language: node_js node_js: - - 4 - - 4.0 - - 6 - - 6.0 - - 7 - - 7.0 + - 8 - 8.0 + - 10 + - 10.0 sudo: false diff --git a/CHANGELOG.md b/CHANGELOG.md old mode 100644 new mode 100755 index 2d784f6e6..3f6bccf8b --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## Changelog +### 3.0.1 +* Updated dependencies + ### 3.0.0 * Complete re-write, with Promises and callback support * Dropped support for node v0.8, v0.10, v0.12 diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 index e67c00666..b2a04b185 --- a/README.md +++ b/README.md @@ -1,7 +1,3 @@ -# This project is seeking maintainer(s) - -## This is a popular project in need of maintenance, please contact @thomseddon on GitHub if you are interested in contributing to this project. - # oauth2-server [![npm Version][npm-image]][npm-url] @@ -26,7 +22,7 @@ The *oauth2-server* module is framework-agnostic but there are several officiall - Supports `authorization_code`, `client_credentials`, `refresh_token` and `password` grant, as well as *extension grants*, with scopes. - Can be used with *promises*, *Node-style callbacks*, *ES6 generators* and *async*/*await* (using [Babel](https://babeljs.io)). -- Fully [RFC 6749](https://tools.ietf.org/html/rfc6749.html) and [RFC 6750](https://tools.ietf.org/html/rfc6750.html) compliant. +- Fully [RFC 6749](https://tools.ietf.org/html/rfc6749.html) and [RFC 6750](https://tools.ietf.org/html/rfc6749.html) compliant. - Implicitly supports any form of storage, e.g. *PostgreSQL*, *MySQL*, *MongoDB*, *Redis*, etc. - Complete [test suite](https://github.com/oauthjs/node-oauth2-server/tree/master/test). diff --git a/docs/Makefile b/docs/Makefile old mode 100644 new mode 100755 diff --git a/docs/_static/custom.css b/docs/_static/custom.css old mode 100644 new mode 100755 diff --git a/docs/_static/favicon.ico b/docs/_static/favicon.ico old mode 100644 new mode 100755 diff --git a/docs/api/errors/access-denied-error.rst b/docs/api/errors/access-denied-error.rst old mode 100644 new mode 100755 diff --git a/docs/api/errors/index.rst b/docs/api/errors/index.rst old mode 100644 new mode 100755 diff --git a/docs/api/errors/insufficient-scope-error.rst b/docs/api/errors/insufficient-scope-error.rst old mode 100644 new mode 100755 diff --git a/docs/api/errors/invalid-argument-error.rst b/docs/api/errors/invalid-argument-error.rst old mode 100644 new mode 100755 diff --git a/docs/api/errors/invalid-client-error.rst b/docs/api/errors/invalid-client-error.rst old mode 100644 new mode 100755 diff --git a/docs/api/errors/invalid-grant-error.rst b/docs/api/errors/invalid-grant-error.rst old mode 100644 new mode 100755 diff --git a/docs/api/errors/invalid-request-error.rst b/docs/api/errors/invalid-request-error.rst old mode 100644 new mode 100755 diff --git a/docs/api/errors/invalid-scope-error.rst b/docs/api/errors/invalid-scope-error.rst old mode 100644 new mode 100755 diff --git a/docs/api/errors/invalid-token-error.rst b/docs/api/errors/invalid-token-error.rst old mode 100644 new mode 100755 diff --git a/docs/api/errors/oauth-error.rst b/docs/api/errors/oauth-error.rst old mode 100644 new mode 100755 diff --git a/docs/api/errors/server-error.rst b/docs/api/errors/server-error.rst old mode 100644 new mode 100755 diff --git a/docs/api/errors/unauthorized-client-error.rst b/docs/api/errors/unauthorized-client-error.rst old mode 100644 new mode 100755 diff --git a/docs/api/errors/unauthorized-request-error.rst b/docs/api/errors/unauthorized-request-error.rst old mode 100644 new mode 100755 diff --git a/docs/api/errors/unsupported-grant-type-error.rst b/docs/api/errors/unsupported-grant-type-error.rst old mode 100644 new mode 100755 diff --git a/docs/api/errors/unsupported-response-type-error.rst b/docs/api/errors/unsupported-response-type-error.rst old mode 100644 new mode 100755 diff --git a/docs/api/oauth2-server.rst b/docs/api/oauth2-server.rst old mode 100644 new mode 100755 diff --git a/docs/api/request.rst b/docs/api/request.rst old mode 100644 new mode 100755 diff --git a/docs/api/response.rst b/docs/api/response.rst old mode 100644 new mode 100755 diff --git a/docs/conf.py b/docs/conf.py old mode 100644 new mode 100755 diff --git a/docs/docs/adapters.rst b/docs/docs/adapters.rst old mode 100644 new mode 100755 diff --git a/docs/docs/getting-started.rst b/docs/docs/getting-started.rst old mode 100644 new mode 100755 diff --git a/docs/index.rst b/docs/index.rst old mode 100644 new mode 100755 diff --git a/docs/make.bat b/docs/make.bat old mode 100644 new mode 100755 diff --git a/docs/misc/extension-grants.rst b/docs/misc/extension-grants.rst old mode 100644 new mode 100755 diff --git a/docs/misc/migrating-v2-to-v3.rst b/docs/misc/migrating-v2-to-v3.rst old mode 100644 new mode 100755 index 199de9383..5a3ec6431 --- a/docs/misc/migrating-v2-to-v3.rst +++ b/docs/misc/migrating-v2-to-v3.rst @@ -40,11 +40,7 @@ The following server options can be set when instantiating the OAuth service: * `allowExtendedTokenAttributes`: **default false** Allows additional attributes (such as `id_token`) to be included in token responses. * `requireClientAuthentication`: **default true for all grant types** Allow ability to set client/secret authentication to `false` for a specific grant type. -The following server options have changed behavior in v3.0.0: - - * `accessTokenLifetime` can no longer be set to `null` to indicate a non-expiring token. The recommend alternative is to set accessTokenLifetime to a high value. - -The following server options have been removed in v3.0.0: +The following server options have been removed in v3.0.0 * `grants`: **removed** (now returned by the `getClient` method). * `debug`: **removed** (not the responsibility of this module). diff --git a/docs/model/overview.rst b/docs/model/overview.rst old mode 100644 new mode 100755 diff --git a/docs/model/spec.rst b/docs/model/spec.rst old mode 100644 new mode 100755 diff --git a/docs/npm_conf.py b/docs/npm_conf.py old mode 100644 new mode 100755 diff --git a/index.js b/index.js deleted file mode 100644 index f4f940ab5..000000000 --- a/index.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict'; - -/** - * Expose server and request/response classes. - */ - -exports = module.exports = require('./lib/server'); -exports.Request = require('./lib/request'); -exports.Response = require('./lib/response'); - -/** - * Export helpers for extension grants. - */ - -exports.AbstractGrantType = require('./lib/grant-types/abstract-grant-type'); - -/** - * Export error classes. - */ - -exports.AccessDeniedError = require('./lib/errors/access-denied-error'); -exports.InsufficientScopeError = require('./lib/errors/insufficient-scope-error'); -exports.InvalidArgumentError = require('./lib/errors/invalid-argument-error'); -exports.InvalidClientError = require('./lib/errors/invalid-client-error'); -exports.InvalidGrantError = require('./lib/errors/invalid-grant-error'); -exports.InvalidRequestError = require('./lib/errors/invalid-request-error'); -exports.InvalidScopeError = require('./lib/errors/invalid-scope-error'); -exports.InvalidTokenError = require('./lib/errors/invalid-token-error'); -exports.OAuthError = require('./lib/errors/oauth-error'); -exports.ServerError = require('./lib/errors/server-error'); -exports.UnauthorizedClientError = require('./lib/errors/unauthorized-client-error'); -exports.UnauthorizedRequestError = require('./lib/errors/unauthorized-request-error'); -exports.UnsupportedGrantTypeError = require('./lib/errors/unsupported-grant-type-error'); -exports.UnsupportedResponseTypeError = require('./lib/errors/unsupported-response-type-error'); - diff --git a/index.ts b/index.ts new file mode 100755 index 000000000..f12e05037 --- /dev/null +++ b/index.ts @@ -0,0 +1,2 @@ +import { OAuth2Server } from './lib/server'; +export default OAuth2Server; diff --git a/lib/errors/access-denied-error.js b/lib/errors/access-denied-error.js deleted file mode 100644 index d3ffc704a..000000000 --- a/lib/errors/access-denied-error.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); - -/** - * Constructor. - * - * "The resource owner or authorization server denied the request" - * - * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 - */ - -function AccessDeniedError(message, properties) { - properties = _.assign({ - code: 400, - name: 'access_denied' - }, properties); - - OAuthError.call(this, message, properties); -} - -/** - * Inherit prototype. - */ - -util.inherits(AccessDeniedError, OAuthError); - -/** - * Export constructor. - */ - -module.exports = AccessDeniedError; diff --git a/lib/errors/access-denied-error.ts b/lib/errors/access-denied-error.ts new file mode 100755 index 000000000..681ca61ab --- /dev/null +++ b/lib/errors/access-denied-error.ts @@ -0,0 +1,15 @@ +import { OAuthError } from './oauth-error'; + +/** + * Constructor. + * + * "The resource owner or authorization server denied the request" + * + * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + */ + +export class AccessDeniedError extends OAuthError { + constructor(message?: string | Error, properties?: any) { + super(message, { code: 400, name: 'access_denied', ...properties }); + } +} diff --git a/lib/errors/insufficient-scope-error.js b/lib/errors/insufficient-scope-error.js deleted file mode 100644 index c6442eac6..000000000 --- a/lib/errors/insufficient-scope-error.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); - -/** - * Constructor. - * - * "The request requires higher privileges than provided by the access token.." - * - * @see https://tools.ietf.org/html/rfc6750.html#section-3.1 - */ - -function InsufficientScopeError(message, properties) { - properties = _.assign({ - code: 403, - name: 'insufficient_scope' - }, properties); - - OAuthError.call(this, message, properties); -} - -/** - * Inherit prototype. - */ - -util.inherits(InsufficientScopeError, OAuthError); - -/** - * Export constructor. - */ - -module.exports = InsufficientScopeError; diff --git a/lib/errors/insufficient-scope-error.ts b/lib/errors/insufficient-scope-error.ts new file mode 100755 index 000000000..f50b4a9e5 --- /dev/null +++ b/lib/errors/insufficient-scope-error.ts @@ -0,0 +1,15 @@ +import { OAuthError } from './oauth-error'; + +/** + * Constructor. + * + * "The request requires higher privileges than provided by the access token.." + * + * @see https://tools.ietf.org/html/rfc6750.html#section-3.1 + */ + +export class InsufficientScopeError extends OAuthError { + constructor(message?: string | Error, properties?: any) { + super(message, { code: 403, name: 'insufficient_scope', ...properties }); + } +} diff --git a/lib/errors/invalid-argument-error.js b/lib/errors/invalid-argument-error.js deleted file mode 100644 index cb56d5a8b..000000000 --- a/lib/errors/invalid-argument-error.js +++ /dev/null @@ -1,34 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); - -/** - * Constructor. - */ - -function InvalidArgumentError(message, properties) { - properties = _.assign({ - code: 500, - name: 'invalid_argument' - }, properties); - - OAuthError.call(this, message, properties); -} - -/** - * Inherit prototype. - */ - -util.inherits(InvalidArgumentError, OAuthError); - -/** - * Export constructor. - */ - -module.exports = InvalidArgumentError; diff --git a/lib/errors/invalid-argument-error.ts b/lib/errors/invalid-argument-error.ts new file mode 100755 index 000000000..b4c5adbd2 --- /dev/null +++ b/lib/errors/invalid-argument-error.ts @@ -0,0 +1,7 @@ +import { OAuthError } from './oauth-error'; + +export class InvalidArgumentError extends OAuthError { + constructor(message?: string | Error, properties?: any) { + super(message, { code: 500, name: 'invalid_argument', ...properties }); + } +} diff --git a/lib/errors/invalid-client-error.js b/lib/errors/invalid-client-error.js deleted file mode 100644 index d95358c7c..000000000 --- a/lib/errors/invalid-client-error.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); - -/** - * Constructor. - * - * "Client authentication failed (e.g., unknown client, no client - * authentication included, or unsupported authentication method)" - * - * @see https://tools.ietf.org/html/rfc6749#section-5.2 - */ - -function InvalidClientError(message, properties) { - properties = _.assign({ - code: 400, - name: 'invalid_client' - }, properties); - - OAuthError.call(this, message, properties); -} - -/** - * Inherit prototype. - */ - -util.inherits(InvalidClientError, OAuthError); - -/** - * Export constructor. - */ - -module.exports = InvalidClientError; diff --git a/lib/errors/invalid-client-error.ts b/lib/errors/invalid-client-error.ts new file mode 100755 index 000000000..8c296a2d0 --- /dev/null +++ b/lib/errors/invalid-client-error.ts @@ -0,0 +1,16 @@ +import { OAuthError } from './oauth-error'; + +/** + * Constructor. + * + * "Client authentication failed (e.g., unknown client, no client + * authentication included, or unsupported authentication method)" + * + * @see https://tools.ietf.org/html/rfc6749#section-5.2 + */ + +export class InvalidClientError extends OAuthError { + constructor(message?: string | Error, properties?: any) { + super(message, { code: 400, name: 'invalid_client', ...properties }); + } +} diff --git a/lib/errors/invalid-grant-error.js b/lib/errors/invalid-grant-error.js deleted file mode 100644 index 58d032e11..000000000 --- a/lib/errors/invalid-grant-error.js +++ /dev/null @@ -1,40 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); - -/** - * Constructor. - * - * "The provided authorization grant (e.g., authorization code, resource owner credentials) - * or refresh token is invalid, expired, revoked, does not match the redirection URI used - * in the authorization request, or was issued to another client." - * - * @see https://tools.ietf.org/html/rfc6749#section-5.2 - */ - -function InvalidGrantError(message, properties) { - properties = _.assign({ - code: 400, - name: 'invalid_grant' - }, properties); - - OAuthError.call(this, message, properties); -} - -/** - * Inherit prototype. - */ - -util.inherits(InvalidGrantError, OAuthError); - -/** - * Export constructor. - */ - -module.exports = InvalidGrantError; diff --git a/lib/errors/invalid-grant-error.ts b/lib/errors/invalid-grant-error.ts new file mode 100755 index 000000000..8c37a3c9f --- /dev/null +++ b/lib/errors/invalid-grant-error.ts @@ -0,0 +1,17 @@ +import { OAuthError } from './oauth-error'; + +/** + * Constructor. + * + * "The provided authorization grant (e.g., authorization code, resource owner credentials) + * or refresh token is invalid, expired, revoked, does not match the redirection URI used + * in the authorization request, or was issued to another client." + * + * @see https://tools.ietf.org/html/rfc6749#section-5.2 + */ + +export class InvalidGrantError extends OAuthError { + constructor(message?: string | Error, properties?: any) { + super(message, { code: 400, name: 'invalid_grant', ...properties }); + } +} diff --git a/lib/errors/invalid-request-error.js b/lib/errors/invalid-request-error.js deleted file mode 100644 index 4cf0a73b1..000000000 --- a/lib/errors/invalid-request-error.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); - -/** - * Constructor. - * - * "The request is missing a required parameter, includes an invalid parameter value, - * includes a parameter more than once, or is otherwise malformed." - * - * @see https://tools.ietf.org/html/rfc6749#section-4.2.2.1 - */ - -function InvalidRequest(message, properties) { - properties = _.assign({ - code: 400, - name: 'invalid_request' - }, properties); - - OAuthError.call(this, message, properties); -} - -/** - * Inherit prototype. - */ - -util.inherits(InvalidRequest, OAuthError); - -/** - * Export constructor. - */ - -module.exports = InvalidRequest; diff --git a/lib/errors/invalid-request-error.ts b/lib/errors/invalid-request-error.ts new file mode 100755 index 000000000..6fe0a253b --- /dev/null +++ b/lib/errors/invalid-request-error.ts @@ -0,0 +1,16 @@ +import { OAuthError } from './oauth-error'; + +/** + * Constructor. + * + * "The request is missing a required parameter, includes an invalid parameter value, + * includes a parameter more than once, or is otherwise malformed." + * + * @see https://tools.ietf.org/html/rfc6749#section-4.2.2.1 + */ + +export class InvalidRequestError extends OAuthError { + constructor(message?: string | Error, properties?: any) { + super(message, { code: 400, name: 'invalid_request', ...properties }); + } +} diff --git a/lib/errors/invalid-scope-error.js b/lib/errors/invalid-scope-error.js deleted file mode 100644 index c3b287fc5..000000000 --- a/lib/errors/invalid-scope-error.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); - -/** - * Constructor. - * - * "The requested scope is invalid, unknown, or malformed." - * - * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 - */ - -function InvalidScopeError(message, properties) { - properties = _.assign({ - code: 400, - name: 'invalid_scope' - }, properties); - - OAuthError.call(this, message, properties); -} - -/** - * Inherit prototype. - */ - -util.inherits(InvalidScopeError, OAuthError); - -/** - * Export constructor. - */ - -module.exports = InvalidScopeError; diff --git a/lib/errors/invalid-scope-error.ts b/lib/errors/invalid-scope-error.ts new file mode 100755 index 000000000..6f3977fb5 --- /dev/null +++ b/lib/errors/invalid-scope-error.ts @@ -0,0 +1,15 @@ +import { OAuthError } from './oauth-error'; + +/** + * Constructor. + * + * "The requested scope is invalid, unknown, or malformed." + * + * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + */ + +export class InvalidScopeError extends OAuthError { + constructor(message?: string | Error, properties?: any) { + super(message, { code: 400, name: 'invalid_scope', ...properties }); + } +} diff --git a/lib/errors/invalid-token-error.js b/lib/errors/invalid-token-error.js deleted file mode 100644 index d7e7a8bfe..000000000 --- a/lib/errors/invalid-token-error.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); - -/** - * Constructor. - * - * "The access token provided is expired, revoked, malformed, or invalid for other reasons." - * - * @see https://tools.ietf.org/html/rfc6750#section-3.1 - */ - -function InvalidTokenError(message, properties) { - properties = _.assign({ - code: 401, - name: 'invalid_token' - }, properties); - - OAuthError.call(this, message, properties); -} - -/** - * Inherit prototype. - */ - -util.inherits(InvalidTokenError, OAuthError); - -/** - * Export constructor. - */ - -module.exports = InvalidTokenError; diff --git a/lib/errors/invalid-token-error.ts b/lib/errors/invalid-token-error.ts new file mode 100755 index 000000000..655e347ce --- /dev/null +++ b/lib/errors/invalid-token-error.ts @@ -0,0 +1,15 @@ +import { OAuthError } from './oauth-error'; + +/** + * Constructor.invalid_token + * + * "The access token provided is expired, revoked, malformed, or invalid for other reasons." + * + * @see https://tools.ietf.org/html/rfc6750#section-3.1 + */ + +export class InvalidTokenError extends OAuthError { + constructor(message?: string | Error, properties?: any) { + super(message, { code: 400, name: 'invalid_token', ...properties }); + } +} diff --git a/lib/errors/oauth-error.js b/lib/errors/oauth-error.js deleted file mode 100644 index cd7d33930..000000000 --- a/lib/errors/oauth-error.js +++ /dev/null @@ -1,45 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ -var _ = require('lodash'); -var util = require('util'); -var statuses = require('statuses'); -/** - * Constructor. - */ - -function OAuthError(messageOrError, properties) { - var message = messageOrError instanceof Error ? messageOrError.message : messageOrError; - var error = messageOrError instanceof Error ? messageOrError : null; - if (_.isEmpty(properties)) - { - properties = {}; - } - - _.defaults(properties, { code: 500 }); - - if (error) { - properties.inner = error; - } - if (_.isEmpty(message)) { - message = statuses[properties.code]; - } - this.code = this.status = this.statusCode = properties.code; - this.message = message; - for (var key in properties) { - if (key !== 'code') { - this[key] = properties[key]; - } - } - Error.captureStackTrace(this, OAuthError); -} - -util.inherits(OAuthError, Error); - -/** - * Export constructor. - */ - -module.exports = OAuthError; diff --git a/lib/errors/oauth-error.ts b/lib/errors/oauth-error.ts new file mode 100755 index 000000000..30151aa08 --- /dev/null +++ b/lib/errors/oauth-error.ts @@ -0,0 +1,35 @@ +import { defaults, isEmpty } from 'lodash'; +import * as statuses from 'statuses'; + +export class OAuthError extends Error { + code: any; + status: any; + statusCode: any; + constructor(messageOrError: string | Error, properties?: any) { + super(); + let message = + messageOrError instanceof Error ? messageOrError.message : messageOrError; + const error = messageOrError instanceof Error ? messageOrError : undefined; + let props: any = {}; + if (!isEmpty(properties)) { + props = properties; + } + + defaults(props, { code: 500 }); + + if (error) { + props.inner = error; + } + if (isEmpty(message)) { + message = statuses[props.code]; + } + this.code = this.status = this.statusCode = props.code; + this.message = message; + for (const key in props) { + if (key !== 'code') { + this[key] = props[key]; + } + } + // Error.captureStackTrace(this, OAuthError); + } +} diff --git a/lib/errors/server-error.js b/lib/errors/server-error.js deleted file mode 100644 index d193af39c..000000000 --- a/lib/errors/server-error.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); - -/** - * Constructor. - * - * "The authorization server encountered an unexpected condition that prevented it from fulfilling the request." - * - * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 - */ - -function ServerError(message, properties) { - properties = _.assign({ - code: 503, - name: 'server_error' - }, properties); - - OAuthError.call(this, message, properties); -} - -/** - * Inherit prototype. - */ - -util.inherits(ServerError, OAuthError); - -/** - * Export constructor. - */ - -module.exports = ServerError; diff --git a/lib/errors/server-error.ts b/lib/errors/server-error.ts new file mode 100755 index 000000000..3d3a478f4 --- /dev/null +++ b/lib/errors/server-error.ts @@ -0,0 +1,15 @@ +import { OAuthError } from './oauth-error'; + +/** + * Constructor. + * + * "The authorization server encountered an unexpected condition that prevented it from fulfilling the request." + * + * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + */ + +export class ServerError extends OAuthError { + constructor(message?: string | Error, properties?: any) { + super(message, { code: 503, name: 'server_error', ...properties }); + } +} diff --git a/lib/errors/unauthorized-client-error.js b/lib/errors/unauthorized-client-error.js deleted file mode 100644 index c05075d3d..000000000 --- a/lib/errors/unauthorized-client-error.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); - -/** - * Constructor. - * - * "The authenticated client is not authorized to use this authorization grant type." - * - * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 - */ - -function UnauthorizedClientError(message, properties) { - properties = _.assign({ - code: 400, - name: 'unauthorized_client' - }, properties); - - OAuthError.call(this, message, properties); -} - -/** - * Inherit prototype. - */ - -util.inherits(UnauthorizedClientError, OAuthError); - -/** - * Export constructor. - */ - -module.exports = UnauthorizedClientError; diff --git a/lib/errors/unauthorized-client-error.ts b/lib/errors/unauthorized-client-error.ts new file mode 100755 index 000000000..e0e5f6a18 --- /dev/null +++ b/lib/errors/unauthorized-client-error.ts @@ -0,0 +1,15 @@ +import { OAuthError } from './oauth-error'; + +/** + * Constructor. + * + * "The authenticated client is not authorized to use this authorization grant type." + * + * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + */ + +export class UnauthorizedClientError extends OAuthError { + constructor(message?: string | Error, properties?: any) { + super(message, { code: 400, name: 'unauthorized_client', ...properties }); + } +} diff --git a/lib/errors/unauthorized-request-error.js b/lib/errors/unauthorized-request-error.js deleted file mode 100644 index ae7500d9c..000000000 --- a/lib/errors/unauthorized-request-error.js +++ /dev/null @@ -1,41 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); - -/** - * Constructor. - * - * "If the request lacks any authentication information (e.g., the client - * was unaware that authentication is necessary or attempted using an - * unsupported authentication method), the resource server SHOULD NOT - * include an error code or other error information." - * - * @see https://tools.ietf.org/html/rfc6750#section-3.1 - */ - -function UnauthorizedRequestError(message, properties) { - properties = _.assign({ - code: 401, - name: 'unauthorized_request' - }, properties); - - OAuthError.call(this, message, properties); -} - -/** - * Inherit prototype. - */ - -util.inherits(UnauthorizedRequestError, OAuthError); - -/** - * Export constructor. - */ - -module.exports = UnauthorizedRequestError; diff --git a/lib/errors/unauthorized-request-error.ts b/lib/errors/unauthorized-request-error.ts new file mode 100755 index 000000000..ba98235da --- /dev/null +++ b/lib/errors/unauthorized-request-error.ts @@ -0,0 +1,18 @@ +import { OAuthError } from './oauth-error'; + +/** + * Constructor. + * + * "If the request lacks any authentication information (e.g., the client + * was unaware that authentication is necessary or attempted using an + * unsupported authentication method), the resource server SHOULD NOT + * include an error code or other error information." + * + * @see https://tools.ietf.org/html/rfc6750#section-3.1 + */ + +export class UnauthorizedRequestError extends OAuthError { + constructor(message?: string | Error, properties?: any) { + super(message, { code: 401, name: 'unauthorized_request', ...properties }); + } +} diff --git a/lib/errors/unsupported-grant-type-error.js b/lib/errors/unsupported-grant-type-error.js deleted file mode 100644 index 28ca0ec04..000000000 --- a/lib/errors/unsupported-grant-type-error.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); - -/** - * Constructor. - * - * "The authorization grant type is not supported by the authorization server." - * - * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 - */ - -function UnsupportedGrantTypeError(message, properties) { - properties = _.assign({ - code: 400, - name: 'unsupported_grant_type' - }, properties); - - OAuthError.call(this, message, properties); -} - -/** - * Inherit prototype. - */ - -util.inherits(UnsupportedGrantTypeError, OAuthError); - -/** - * Export constructor. - */ - -module.exports = UnsupportedGrantTypeError; diff --git a/lib/errors/unsupported-grant-type-error.ts b/lib/errors/unsupported-grant-type-error.ts new file mode 100755 index 000000000..7a249d343 --- /dev/null +++ b/lib/errors/unsupported-grant-type-error.ts @@ -0,0 +1,19 @@ +import { OAuthError } from './oauth-error'; + +/** + * Constructor. + * + * "The authorization grant type is not supported by the authorization server." + * + * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + */ + +export class UnsupportedGrantTypeError extends OAuthError { + constructor(message?: string | Error, properties?: any) { + super(message, { + code: 400, + name: 'unsupported_grant_type', + ...properties, + }); + } +} diff --git a/lib/errors/unsupported-response-type-error.js b/lib/errors/unsupported-response-type-error.js deleted file mode 100644 index 523cc449b..000000000 --- a/lib/errors/unsupported-response-type-error.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); - -/** - * Constructor. - * - * "The authorization server does not supported obtaining an - * authorization code using this method." - * - * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 - */ - -function UnsupportedResponseTypeError(message, properties) { - properties = _.assign({ - code: 400, - name: 'unsupported_response_type' - }, properties); - - OAuthError.call(this, message, properties); -} - -/** - * Inherit prototype. - */ - -util.inherits(UnsupportedResponseTypeError, OAuthError); - -/** - * Export constructor. - */ - -module.exports = UnsupportedResponseTypeError; diff --git a/lib/errors/unsupported-response-type-error.ts b/lib/errors/unsupported-response-type-error.ts new file mode 100755 index 000000000..14c3f9e83 --- /dev/null +++ b/lib/errors/unsupported-response-type-error.ts @@ -0,0 +1,20 @@ +import { OAuthError } from './oauth-error'; + +/** + * Constructor. + * + * "The authorization server does not supported obtaining an + * authorization code using this method." + * + * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + */ + +export class UnsupportedResponseTypeError extends OAuthError { + constructor(message?: string | Error, properties?: any) { + super(message, { + code: 400, + name: 'unsupported_response_type', + ...properties, + }); + } +} diff --git a/lib/grant-types/abstract-grant-type.js b/lib/grant-types/abstract-grant-type.js deleted file mode 100644 index be4259dec..000000000 --- a/lib/grant-types/abstract-grant-type.js +++ /dev/null @@ -1,123 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var InvalidArgumentError = require('../errors/invalid-argument-error'); -var InvalidScopeError = require('../errors/invalid-scope-error'); -var Promise = require('bluebird'); -var promisify = require('promisify-any').use(Promise); -var is = require('../validator/is'); -var tokenUtil = require('../utils/token-util'); - -/** - * Constructor. - */ - -function AbstractGrantType(options) { - options = options || {}; - - if (!options.accessTokenLifetime) { - throw new InvalidArgumentError('Missing parameter: `accessTokenLifetime`'); - } - - if (!options.model) { - throw new InvalidArgumentError('Missing parameter: `model`'); - } - - this.accessTokenLifetime = options.accessTokenLifetime; - this.model = options.model; - this.refreshTokenLifetime = options.refreshTokenLifetime; - this.alwaysIssueNewRefreshToken = options.alwaysIssueNewRefreshToken; -} - -/** - * Generate access token. - */ - -AbstractGrantType.prototype.generateAccessToken = function(client, user, scope) { - if (this.model.generateAccessToken) { - return promisify(this.model.generateAccessToken, 3).call(this.model, client, user, scope) - .then(function(accessToken) { - return accessToken || tokenUtil.generateRandomToken(); - }); - } - - return tokenUtil.generateRandomToken(); -}; - -/** - * Generate refresh token. - */ - -AbstractGrantType.prototype.generateRefreshToken = function(client, user, scope) { - if (this.model.generateRefreshToken) { - return promisify(this.model.generateRefreshToken, 3).call(this.model, client, user, scope) - .then(function(refreshToken) { - return refreshToken || tokenUtil.generateRandomToken(); - }); - } - - return tokenUtil.generateRandomToken(); -}; - -/** - * Get access token expiration date. - */ - -AbstractGrantType.prototype.getAccessTokenExpiresAt = function() { - var expires = new Date(); - - expires.setSeconds(expires.getSeconds() + this.accessTokenLifetime); - - return expires; -}; - -/** - * Get refresh token expiration date. - */ - -AbstractGrantType.prototype.getRefreshTokenExpiresAt = function() { - var expires = new Date(); - - expires.setSeconds(expires.getSeconds() + this.refreshTokenLifetime); - - return expires; -}; - -/** - * Get scope from the request body. - */ - -AbstractGrantType.prototype.getScope = function(request) { - if (!is.nqschar(request.body.scope)) { - throw new InvalidArgumentError('Invalid parameter: `scope`'); - } - - return request.body.scope; -}; - -/** - * Validate requested scope. - */ -AbstractGrantType.prototype.validateScope = function(user, client, scope) { - if (this.model.validateScope) { - return promisify(this.model.validateScope, 3).call(this.model, user, client, scope) - .then(function (scope) { - if (!scope) { - throw new InvalidScopeError('Invalid scope: Requested scope is invalid'); - } - - return scope; - }); - } else { - return scope; - } -}; - -/** - * Export constructor. - */ - -module.exports = AbstractGrantType; diff --git a/lib/grant-types/abstract-grant-type.ts b/lib/grant-types/abstract-grant-type.ts new file mode 100755 index 000000000..6942a390e --- /dev/null +++ b/lib/grant-types/abstract-grant-type.ts @@ -0,0 +1,112 @@ +import { InvalidArgumentError } from '../errors/invalid-argument-error'; +import { InvalidScopeError } from '../errors/invalid-scope-error'; +import { Client } from '../interfaces/client.interface'; +import { Model } from '../interfaces/model.interface'; +import { User } from '../interfaces/user.interface'; +import { Request } from '../request'; +import * as tokenUtil from '../utils/token-util'; +import * as is from '../validator/is'; + +export class AbstractGrantType { + accessTokenLifetime: number; + model: Model; + refreshTokenLifetime: number; + alwaysIssueNewRefreshToken: boolean; + + constructor(options: any = {}) { + if (!options.accessTokenLifetime) { + throw new InvalidArgumentError( + 'Missing parameter: `accessTokenLifetime`', + ); + } + + if (!options.model) { + throw new InvalidArgumentError('Missing parameter: `model`'); + } + + this.accessTokenLifetime = options.accessTokenLifetime; + this.model = options.model; + this.refreshTokenLifetime = options.refreshTokenLifetime; + this.alwaysIssueNewRefreshToken = options.alwaysIssueNewRefreshToken; + } + + /** + * Generate access token. + */ + + async generateAccessToken(client?: Client, user?: User, scope?: string) { + if (this.model.generateAccessToken) { + const token = await this.model.generateAccessToken(client, user, scope); + + return token || tokenUtil.GenerateRandomToken(); + } + + return tokenUtil.GenerateRandomToken(); + } + + /** + * Generate refresh token. + */ + + async generateRefreshToken(client?: Client, user?: User, scope?: string) { + if (this.model.generateRefreshToken) { + const token = await this.model.generateRefreshToken(client, user, scope); + + return token || tokenUtil.GenerateRandomToken(); + } + + return tokenUtil.GenerateRandomToken(); + } + + /** + * Get access token expiration date. + */ + + getAccessTokenExpiresAt() { + const expires = new Date(); + expires.setSeconds(expires.getSeconds() + this.accessTokenLifetime); + + return expires; + } + + /** + * Get refresh token expiration date. + */ + + getRefreshTokenExpiresAt() { + const expires = new Date(); + expires.setSeconds(expires.getSeconds() + this.refreshTokenLifetime); + + return expires; + } + + /** + * Get scope from the request body. + */ + + getScope = (request: Request) => { + if (!is.nqschar(request.body.scope)) { + throw new InvalidArgumentError('Invalid parameter: `scope`'); + } + + return request.body.scope; + }; + + /** + * Validate requested scope. + */ + async validateScope(user: User, client: Client, scope: string) { + if (this.model.validateScope) { + const sc = await this.model.validateScope(user, client, scope); + if (!sc) { + throw new InvalidScopeError( + 'Invalid scope: Requested scope is invalid', + ); + } + + return sc; + } + + return scope; + } +} diff --git a/lib/grant-types/authorization-code-grant-type.js b/lib/grant-types/authorization-code-grant-type.js deleted file mode 100644 index 7eae70f8f..000000000 --- a/lib/grant-types/authorization-code-grant-type.js +++ /dev/null @@ -1,206 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var AbstractGrantType = require('./abstract-grant-type'); -var InvalidArgumentError = require('../errors/invalid-argument-error'); -var InvalidGrantError = require('../errors/invalid-grant-error'); -var InvalidRequestError = require('../errors/invalid-request-error'); -var Promise = require('bluebird'); -var promisify = require('promisify-any').use(Promise); -var ServerError = require('../errors/server-error'); -var is = require('../validator/is'); -var util = require('util'); - -/** - * Constructor. - */ - -function AuthorizationCodeGrantType(options) { - options = options || {}; - - if (!options.model) { - throw new InvalidArgumentError('Missing parameter: `model`'); - } - - if (!options.model.getAuthorizationCode) { - throw new InvalidArgumentError('Invalid argument: model does not implement `getAuthorizationCode()`'); - } - - if (!options.model.revokeAuthorizationCode) { - throw new InvalidArgumentError('Invalid argument: model does not implement `revokeAuthorizationCode()`'); - } - - if (!options.model.saveToken) { - throw new InvalidArgumentError('Invalid argument: model does not implement `saveToken()`'); - } - - AbstractGrantType.call(this, options); -} - -/** - * Inherit prototype. - */ - -util.inherits(AuthorizationCodeGrantType, AbstractGrantType); - -/** - * Handle authorization code grant. - * - * @see https://tools.ietf.org/html/rfc6749#section-4.1.3 - */ - -AuthorizationCodeGrantType.prototype.handle = function(request, client) { - if (!request) { - throw new InvalidArgumentError('Missing parameter: `request`'); - } - - if (!client) { - throw new InvalidArgumentError('Missing parameter: `client`'); - } - - return Promise.bind(this) - .then(function() { - return this.getAuthorizationCode(request, client); - }) - .tap(function(code) { - return this.validateRedirectUri(request, code); - }) - .tap(function(code) { - return this.revokeAuthorizationCode(code); - }) - .then(function(code) { - return this.saveToken(code.user, client, code.authorizationCode, code.scope); - }); -}; - -/** - * Get the authorization code. - */ - -AuthorizationCodeGrantType.prototype.getAuthorizationCode = function(request, client) { - if (!request.body.code) { - throw new InvalidRequestError('Missing parameter: `code`'); - } - - if (!is.vschar(request.body.code)) { - throw new InvalidRequestError('Invalid parameter: `code`'); - } - return promisify(this.model.getAuthorizationCode, 1).call(this.model, request.body.code) - .then(function(code) { - if (!code) { - throw new InvalidGrantError('Invalid grant: authorization code is invalid'); - } - - if (!code.client) { - throw new ServerError('Server error: `getAuthorizationCode()` did not return a `client` object'); - } - - if (!code.user) { - throw new ServerError('Server error: `getAuthorizationCode()` did not return a `user` object'); - } - - if (code.client.id !== client.id) { - throw new InvalidGrantError('Invalid grant: authorization code is invalid'); - } - - if (!(code.expiresAt instanceof Date)) { - throw new ServerError('Server error: `expiresAt` must be a Date instance'); - } - - if (code.expiresAt < new Date()) { - throw new InvalidGrantError('Invalid grant: authorization code has expired'); - } - - if (code.redirectUri && !is.uri(code.redirectUri)) { - throw new InvalidGrantError('Invalid grant: `redirect_uri` is not a valid URI'); - } - - return code; - }); -}; - -/** - * Validate the redirect URI. - * - * "The authorization server MUST ensure that the redirect_uri parameter is - * present if the redirect_uri parameter was included in the initial - * authorization request as described in Section 4.1.1, and if included - * ensure that their values are identical." - * - * @see https://tools.ietf.org/html/rfc6749#section-4.1.3 - */ - - AuthorizationCodeGrantType.prototype.validateRedirectUri = function(request, code) { - if (!code.redirectUri) { - return; - } - - var redirectUri = request.body.redirect_uri || request.query.redirect_uri; - - if (!is.uri(redirectUri)) { - throw new InvalidRequestError('Invalid request: `redirect_uri` is not a valid URI'); - } - - if (redirectUri !== code.redirectUri) { - throw new InvalidRequestError('Invalid request: `redirect_uri` is invalid'); - } - }; - -/** - * Revoke the authorization code. - * - * "The authorization code MUST expire shortly after it is issued to mitigate - * the risk of leaks. [...] If an authorization code is used more than once, - * the authorization server MUST deny the request." - * - * @see https://tools.ietf.org/html/rfc6749#section-4.1.2 - */ - -AuthorizationCodeGrantType.prototype.revokeAuthorizationCode = function(code) { - return promisify(this.model.revokeAuthorizationCode, 1).call(this.model, code) - .then(function(status) { - if (!status) { - throw new InvalidGrantError('Invalid grant: authorization code is invalid'); - } - - return code; - }); -}; - -/** - * Save token. - */ - -AuthorizationCodeGrantType.prototype.saveToken = function(user, client, authorizationCode, scope) { - var fns = [ - this.validateScope(user, client, scope), - this.generateAccessToken(client, user, scope), - this.generateRefreshToken(client, user, scope), - this.getAccessTokenExpiresAt(), - this.getRefreshTokenExpiresAt() - ]; - - return Promise.all(fns) - .bind(this) - .spread(function(scope, accessToken, refreshToken, accessTokenExpiresAt, refreshTokenExpiresAt) { - var token = { - accessToken: accessToken, - authorizationCode: authorizationCode, - accessTokenExpiresAt: accessTokenExpiresAt, - refreshToken: refreshToken, - refreshTokenExpiresAt: refreshTokenExpiresAt, - scope: scope - }; - - return promisify(this.model.saveToken, 3).call(this.model, token, client, user); - }); -}; - -/** - * Export constructor. - */ - -module.exports = AuthorizationCodeGrantType; diff --git a/lib/grant-types/authorization-code-grant-type.ts b/lib/grant-types/authorization-code-grant-type.ts new file mode 100755 index 000000000..33a99f7d8 --- /dev/null +++ b/lib/grant-types/authorization-code-grant-type.ts @@ -0,0 +1,212 @@ +import { InvalidArgumentError } from '../errors/invalid-argument-error'; +import { InvalidGrantError } from '../errors/invalid-grant-error'; +import { InvalidRequestError } from '../errors/invalid-request-error'; +import { ServerError } from '../errors/server-error'; +import { Client } from '../interfaces/client.interface'; +import { Token } from '../interfaces/token.interface'; +import { User } from '../interfaces/user.interface'; +import { Request } from '../request'; +import * as is from '../validator/is'; +import { AbstractGrantType } from './abstract-grant-type'; + +export class AuthorizationCodeGrantType extends AbstractGrantType { + constructor(options: any = {}) { + super(options); + if (!options.model) { + throw new InvalidArgumentError('Missing parameter: `model`'); + } + + if (!options.model.getAuthorizationCode) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `getAuthorizationCode()`', + ); + } + + if (!options.model.revokeAuthorizationCode) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `revokeAuthorizationCode()`', + ); + } + + if (!options.model.saveToken) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `saveToken()`', + ); + } + } + + /** + * Handle authorization code grant. + * + * @see https://tools.ietf.org/html/rfc6749#section-4.1.3 + */ + + async handle(request: Request, client) { + if (!request) { + throw new InvalidArgumentError('Missing parameter: `request`'); + } + + if (!client) { + throw new InvalidArgumentError('Missing parameter: `client`'); + } + const code = await this.getAuthorizationCode(request, client); + this.validateRedirectUri(request, code); + await this.revokeAuthorizationCode(code); + + return this.saveToken( + code.user, + client, + code.authorizationCode, + code.scope, + ); + } + + /** + * Get the authorization code. + */ + + async getAuthorizationCode(request: Request, client) { + if (!request.body.code) { + throw new InvalidRequestError('Missing parameter: `code`'); + } + + if (!is.vschar(request.body.code)) { + throw new InvalidRequestError('Invalid parameter: `code`'); + } + + const code = await this.model.getAuthorizationCode(request.body.code); + if (!code) { + throw new InvalidGrantError( + 'Invalid grant: authorization code is invalid', + ); + } + + if (!code.client) { + throw new ServerError( + 'Server error: `getAuthorizationCode()` did not return a `client` object', + ); + } + + if (!code.user) { + throw new ServerError( + 'Server error: `getAuthorizationCode()` did not return a `user` object', + ); + } + + if (code.client.id !== client.id) { + throw new InvalidGrantError( + 'Invalid grant: authorization code is invalid', + ); + } + + if (!(code.expiresAt instanceof Date)) { + throw new ServerError( + 'Server error: `expiresAt` must be a Date instance', + ); + } + + if (code.expiresAt < new Date()) { + throw new InvalidGrantError( + 'Invalid grant: authorization code has expired', + ); + } + + if (code.redirectUri && !is.uri(code.redirectUri)) { + throw new InvalidGrantError( + 'Invalid grant: `redirect_uri` is not a valid URI', + ); + } + + return code; + } + + /** + * Validate the redirect URI. + * + * "The authorization server MUST ensure that the redirect_uri parameter is + * present if the redirect_uri parameter was included in the initial + * authorization request as described in Section 4.1.1, and if included + * ensure that their values are identical." + * + * @see https://tools.ietf.org/html/rfc6749#section-4.1.3 + */ + + validateRedirectUri = (request: Request, code) => { + if (!code.redirectUri) { + return; + } + + const redirectUri = request.body.redirect_uri || request.query.redirect_uri; + + if (!is.uri(redirectUri)) { + throw new InvalidRequestError( + 'Invalid request: `redirect_uri` is not a valid URI', + ); + } + + if (redirectUri !== code.redirectUri) { + throw new InvalidRequestError( + 'Invalid request: `redirect_uri` is invalid', + ); + } + }; + + /** + * Revoke the authorization code. + * + * "The authorization code MUST expire shortly after it is issued to mitigate + * the risk of leaks. [...] If an authorization code is used more than once, + * the authorization server MUST deny the request." + * + * @see https://tools.ietf.org/html/rfc6749#section-4.1.2 + */ + + async revokeAuthorizationCode(code) { + const status = await this.model.revokeAuthorizationCode(code); + if (!status) { + throw new InvalidGrantError( + 'Invalid grant: authorization code is invalid', + ); + } + + return code; + } + + /** + * Save token. + */ + + async saveToken( + user: User, + client: Client, + authorizationCode: string, + scope: string, + ) { + const fns = [ + this.validateScope(user, client, scope), + this.generateAccessToken(client, user, scope), + this.generateRefreshToken(client, user, scope), + this.getAccessTokenExpiresAt(), + this.getRefreshTokenExpiresAt(), + ]; + + const [ + accessScope, + accessToken, + refreshToken, + accessTokenExpiresAt, + refreshTokenExpiresAt, + ] = await Promise.all(fns as any); + + const token: Token = { + accessToken, + authorizationCode, + accessTokenExpiresAt, + refreshToken, + refreshTokenExpiresAt, + scope: accessScope, + } as any; + + return this.model.saveToken(token, client, user); + } +} diff --git a/lib/grant-types/client-credentials-grant-type.js b/lib/grant-types/client-credentials-grant-type.js deleted file mode 100644 index 138333e50..000000000 --- a/lib/grant-types/client-credentials-grant-type.js +++ /dev/null @@ -1,111 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var AbstractGrantType = require('./abstract-grant-type'); -var InvalidArgumentError = require('../errors/invalid-argument-error'); -var InvalidGrantError = require('../errors/invalid-grant-error'); -var Promise = require('bluebird'); -var promisify = require('promisify-any').use(Promise); -var util = require('util'); - -/** - * Constructor. - */ - -function ClientCredentialsGrantType(options) { - options = options || {}; - - if (!options.model) { - throw new InvalidArgumentError('Missing parameter: `model`'); - } - - if (!options.model.getUserFromClient) { - throw new InvalidArgumentError('Invalid argument: model does not implement `getUserFromClient()`'); - } - - if (!options.model.saveToken) { - throw new InvalidArgumentError('Invalid argument: model does not implement `saveToken()`'); - } - - AbstractGrantType.call(this, options); -} - -/** - * Inherit prototype. - */ - -util.inherits(ClientCredentialsGrantType, AbstractGrantType); - -/** - * Handle client credentials grant. - * - * @see https://tools.ietf.org/html/rfc6749#section-4.4.2 - */ - -ClientCredentialsGrantType.prototype.handle = function(request, client) { - if (!request) { - throw new InvalidArgumentError('Missing parameter: `request`'); - } - - if (!client) { - throw new InvalidArgumentError('Missing parameter: `client`'); - } - - var scope = this.getScope(request); - - return Promise.bind(this) - .then(function() { - return this.getUserFromClient(client); - }) - .then(function(user) { - return this.saveToken(user, client, scope); - }); -}; - -/** - * Retrieve the user using client credentials. - */ - -ClientCredentialsGrantType.prototype.getUserFromClient = function(client) { - return promisify(this.model.getUserFromClient, 1).call(this.model, client) - .then(function(user) { - if (!user) { - throw new InvalidGrantError('Invalid grant: user credentials are invalid'); - } - - return user; - }); -}; - -/** - * Save token. - */ - -ClientCredentialsGrantType.prototype.saveToken = function(user, client, scope) { - var fns = [ - this.validateScope(user, client, scope), - this.generateAccessToken(client, user, scope), - this.getAccessTokenExpiresAt(client, user, scope) - ]; - - return Promise.all(fns) - .bind(this) - .spread(function(scope, accessToken, accessTokenExpiresAt) { - var token = { - accessToken: accessToken, - accessTokenExpiresAt: accessTokenExpiresAt, - scope: scope - }; - - return promisify(this.model.saveToken, 3).call(this.model, token, client, user); - }); -}; - -/** - * Export constructor. - */ - -module.exports = ClientCredentialsGrantType; diff --git a/lib/grant-types/client-credentials-grant-type.ts b/lib/grant-types/client-credentials-grant-type.ts new file mode 100755 index 000000000..e4d4cebd4 --- /dev/null +++ b/lib/grant-types/client-credentials-grant-type.ts @@ -0,0 +1,88 @@ +import { InvalidArgumentError } from '../errors/invalid-argument-error'; +import { InvalidGrantError } from '../errors/invalid-grant-error'; +import { Client } from '../interfaces/client.interface'; +import { Token } from '../interfaces/token.interface'; +import { User } from '../interfaces/user.interface'; +import { Request } from '../request'; +import { AbstractGrantType } from './abstract-grant-type'; + +export class ClientCredentialsGrantType extends AbstractGrantType { + constructor(options: any = {}) { + super(options); + if (!options.model) { + throw new InvalidArgumentError('Missing parameter: `model`'); + } + + if (!options.model.getUserFromClient) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `getUserFromClient()`', + ); + } + + if (!options.model.saveToken) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `saveToken()`', + ); + } + } + + /** + * Handle client credentials grant. + * + * @see https://tools.ietf.org/html/rfc6749#section-4.4.2 + */ + + async handle(request: Request, client: Client) { + if (!request) { + throw new InvalidArgumentError('Missing parameter: `request`'); + } + + if (!client) { + throw new InvalidArgumentError('Missing parameter: `client`'); + } + + const scope = this.getScope(request); + const user = await this.getUserFromClient(client); + + return this.saveToken(user, client, scope); + } + + /** + * Retrieve the user using client credentials. + */ + + async getUserFromClient(client: Client) { + const user = await this.model.getUserFromClient(client); + if (!user) { + throw new InvalidGrantError( + 'Invalid grant: user credentials are invalid', + ); + } + + return user; + } + + /** + * Save token. + */ + + async saveToken(user: User, client: Client, scope: string) { + const fns = [ + this.validateScope(user, client, scope), + this.generateAccessToken(client, user, scope), + this.getAccessTokenExpiresAt(), + ]; + + const [accessScope, accessToken, accessTokenExpiresAt] = await Promise.all( + fns as any, + ); + + const token = { + accessToken, + accessTokenExpiresAt, + scope: accessScope, + } as Token; + + return this.model.saveToken(token, client, user); + } +} diff --git a/lib/grant-types/password-grant-type.js b/lib/grant-types/password-grant-type.js deleted file mode 100644 index b7f17935b..000000000 --- a/lib/grant-types/password-grant-type.js +++ /dev/null @@ -1,133 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var AbstractGrantType = require('./abstract-grant-type'); -var InvalidArgumentError = require('../errors/invalid-argument-error'); -var InvalidGrantError = require('../errors/invalid-grant-error'); -var InvalidRequestError = require('../errors/invalid-request-error'); -var Promise = require('bluebird'); -var promisify = require('promisify-any').use(Promise); -var is = require('../validator/is'); -var util = require('util'); - -/** - * Constructor. - */ - -function PasswordGrantType(options) { - options = options || {}; - - if (!options.model) { - throw new InvalidArgumentError('Missing parameter: `model`'); - } - - if (!options.model.getUser) { - throw new InvalidArgumentError('Invalid argument: model does not implement `getUser()`'); - } - - if (!options.model.saveToken) { - throw new InvalidArgumentError('Invalid argument: model does not implement `saveToken()`'); - } - - AbstractGrantType.call(this, options); -} - -/** - * Inherit prototype. - */ - -util.inherits(PasswordGrantType, AbstractGrantType); - -/** - * Retrieve the user from the model using a username/password combination. - * - * @see https://tools.ietf.org/html/rfc6749#section-4.3.2 - */ - -PasswordGrantType.prototype.handle = function(request, client) { - if (!request) { - throw new InvalidArgumentError('Missing parameter: `request`'); - } - - if (!client) { - throw new InvalidArgumentError('Missing parameter: `client`'); - } - - var scope = this.getScope(request); - - return Promise.bind(this) - .then(function() { - return this.getUser(request); - }) - .then(function(user) { - return this.saveToken(user, client, scope); - }); -}; - -/** - * Get user using a username/password combination. - */ - -PasswordGrantType.prototype.getUser = function(request) { - if (!request.body.username) { - throw new InvalidRequestError('Missing parameter: `username`'); - } - - if (!request.body.password) { - throw new InvalidRequestError('Missing parameter: `password`'); - } - - if (!is.uchar(request.body.username)) { - throw new InvalidRequestError('Invalid parameter: `username`'); - } - - if (!is.uchar(request.body.password)) { - throw new InvalidRequestError('Invalid parameter: `password`'); - } - - return promisify(this.model.getUser, 2).call(this.model, request.body.username, request.body.password) - .then(function(user) { - if (!user) { - throw new InvalidGrantError('Invalid grant: user credentials are invalid'); - } - - return user; - }); -}; - -/** - * Save token. - */ - -PasswordGrantType.prototype.saveToken = function(user, client, scope) { - var fns = [ - this.validateScope(user, client, scope), - this.generateAccessToken(client, user, scope), - this.generateRefreshToken(client, user, scope), - this.getAccessTokenExpiresAt(), - this.getRefreshTokenExpiresAt() - ]; - - return Promise.all(fns) - .bind(this) - .spread(function(scope, accessToken, refreshToken, accessTokenExpiresAt, refreshTokenExpiresAt) { - var token = { - accessToken: accessToken, - accessTokenExpiresAt: accessTokenExpiresAt, - refreshToken: refreshToken, - refreshTokenExpiresAt: refreshTokenExpiresAt, - scope: scope - }; - - return promisify(this.model.saveToken, 3).call(this.model, token, client, user); - }); -}; - -/** - * Export constructor. - */ - -module.exports = PasswordGrantType; diff --git a/lib/grant-types/password-grant-type.ts b/lib/grant-types/password-grant-type.ts new file mode 100755 index 000000000..ff7202d0a --- /dev/null +++ b/lib/grant-types/password-grant-type.ts @@ -0,0 +1,118 @@ +import { InvalidArgumentError } from '../errors/invalid-argument-error'; +import { InvalidGrantError } from '../errors/invalid-grant-error'; +import { InvalidRequestError } from '../errors/invalid-request-error'; +import { Client } from '../interfaces/client.interface'; +import { Token } from '../interfaces/token.interface'; +import { User } from '../interfaces/user.interface'; +import { Request } from '../request'; +import * as is from '../validator/is'; +import { AbstractGrantType } from './abstract-grant-type'; + +export class PasswordGrantType extends AbstractGrantType { + constructor(options: any = {}) { + super(options); + + if (!options.model) { + throw new InvalidArgumentError('Missing parameter: `model`'); + } + + if (!options.model.getUser) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `getUser()`', + ); + } + + if (!options.model.saveToken) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `saveToken()`', + ); + } + } + + /** + * Retrieve the user from the model using a username/password combination. + * + * @see https://tools.ietf.org/html/rfc6749#section-4.3.2 + */ + + async handle(request, client) { + if (!request) { + throw new InvalidArgumentError('Missing parameter: `request`'); + } + + if (!client) { + throw new InvalidArgumentError('Missing parameter: `client`'); + } + + const scope = this.getScope(request); + const user = await this.getUser(request); + + return this.saveToken(user, client, scope); + } + + /** + * Get user using a username/password combination. + */ + + async getUser(request: Request) { + if (!request.body.username) { + throw new InvalidRequestError('Missing parameter: `username`'); + } + + if (!request.body.password) { + throw new InvalidRequestError('Missing parameter: `password`'); + } + + if (!is.uchar(request.body.username)) { + throw new InvalidRequestError('Invalid parameter: `username`'); + } + + if (!is.uchar(request.body.password)) { + throw new InvalidRequestError('Invalid parameter: `password`'); + } + + const user = await this.model.getUser( + request.body.username, + request.body.password, + ); + if (!user) { + throw new InvalidGrantError( + 'Invalid grant: user credentials are invalid', + ); + } + + return user; + } + + /** + * Save token. + */ + + async saveToken(user: User, client: Client, scope: string) { + const fns = [ + this.validateScope(user, client, scope), + this.generateAccessToken(client, user, scope), + this.generateRefreshToken(client, user, scope), + this.getAccessTokenExpiresAt(), + this.getRefreshTokenExpiresAt(), + ]; + + const [ + accessScope, + accessToken, + refreshToken, + accessTokenExpiresAt, + refreshTokenExpiresAt, + ] = await Promise.all(fns as any); + + const token = { + accessToken, + accessTokenExpiresAt, + refreshToken, + refreshTokenExpiresAt, + scope: accessScope, + } as Token; + + return this.model.saveToken(token, client, user); + } +} diff --git a/lib/grant-types/refresh-token-grant-type.js b/lib/grant-types/refresh-token-grant-type.js deleted file mode 100644 index 19f9010c2..000000000 --- a/lib/grant-types/refresh-token-grant-type.js +++ /dev/null @@ -1,180 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var AbstractGrantType = require('./abstract-grant-type'); -var InvalidArgumentError = require('../errors/invalid-argument-error'); -var InvalidGrantError = require('../errors/invalid-grant-error'); -var InvalidRequestError = require('../errors/invalid-request-error'); -var Promise = require('bluebird'); -var promisify = require('promisify-any').use(Promise); -var ServerError = require('../errors/server-error'); -var is = require('../validator/is'); -var util = require('util'); - -/** - * Constructor. - */ - -function RefreshTokenGrantType(options) { - options = options || {}; - - if (!options.model) { - throw new InvalidArgumentError('Missing parameter: `model`'); - } - - if (!options.model.getRefreshToken) { - throw new InvalidArgumentError('Invalid argument: model does not implement `getRefreshToken()`'); - } - - if (!options.model.revokeToken) { - throw new InvalidArgumentError('Invalid argument: model does not implement `revokeToken()`'); - } - - if (!options.model.saveToken) { - throw new InvalidArgumentError('Invalid argument: model does not implement `saveToken()`'); - } - - AbstractGrantType.call(this, options); -} - -/** - * Inherit prototype. - */ - -util.inherits(RefreshTokenGrantType, AbstractGrantType); - -/** - * Handle refresh token grant. - * - * @see https://tools.ietf.org/html/rfc6749#section-6 - */ - -RefreshTokenGrantType.prototype.handle = function(request, client) { - if (!request) { - throw new InvalidArgumentError('Missing parameter: `request`'); - } - - if (!client) { - throw new InvalidArgumentError('Missing parameter: `client`'); - } - - return Promise.bind(this) - .then(function() { - return this.getRefreshToken(request, client); - }) - .tap(function(token) { - return this.revokeToken(token); - }) - .then(function(token) { - return this.saveToken(token.user, client, token.scope); - }); -}; - -/** - * Get refresh token. - */ - -RefreshTokenGrantType.prototype.getRefreshToken = function(request, client) { - if (!request.body.refresh_token) { - throw new InvalidRequestError('Missing parameter: `refresh_token`'); - } - - if (!is.vschar(request.body.refresh_token)) { - throw new InvalidRequestError('Invalid parameter: `refresh_token`'); - } - - return promisify(this.model.getRefreshToken, 1).call(this.model, request.body.refresh_token) - .then(function(token) { - if (!token) { - throw new InvalidGrantError('Invalid grant: refresh token is invalid'); - } - - if (!token.client) { - throw new ServerError('Server error: `getRefreshToken()` did not return a `client` object'); - } - - if (!token.user) { - throw new ServerError('Server error: `getRefreshToken()` did not return a `user` object'); - } - - if (token.client.id !== client.id) { - throw new InvalidGrantError('Invalid grant: refresh token is invalid'); - } - - if (token.refreshTokenExpiresAt && !(token.refreshTokenExpiresAt instanceof Date)) { - throw new ServerError('Server error: `refreshTokenExpiresAt` must be a Date instance'); - } - - if (token.refreshTokenExpiresAt && token.refreshTokenExpiresAt < new Date()) { - throw new InvalidGrantError('Invalid grant: refresh token has expired'); - } - - return token; - }); -}; - -/** - * Revoke the refresh token. - * - * @see https://tools.ietf.org/html/rfc6749#section-6 - */ - -RefreshTokenGrantType.prototype.revokeToken = function(token) { - if (this.alwaysIssueNewRefreshToken === false) { - return Promise.resolve(token); - } - - return promisify(this.model.revokeToken, 1).call(this.model, token) - .then(function(status) { - if (!status) { - throw new InvalidGrantError('Invalid grant: refresh token is invalid'); - } - - return token; - }); -}; - -/** - * Save token. - */ - -RefreshTokenGrantType.prototype.saveToken = function(user, client, scope) { - var fns = [ - this.generateAccessToken(client, user, scope), - this.generateRefreshToken(client, user, scope), - this.getAccessTokenExpiresAt(), - this.getRefreshTokenExpiresAt() - ]; - - return Promise.all(fns) - .bind(this) - .spread(function(accessToken, refreshToken, accessTokenExpiresAt, refreshTokenExpiresAt) { - var token = { - accessToken: accessToken, - accessTokenExpiresAt: accessTokenExpiresAt, - scope: scope - }; - - if (this.alwaysIssueNewRefreshToken !== false) { - token.refreshToken = refreshToken; - token.refreshTokenExpiresAt = refreshTokenExpiresAt; - } - - return token; - }) - .then(function(token) { - return promisify(this.model.saveToken, 3).call(this.model, token, client, user) - .then(function(savedToken) { - return savedToken; - }); - }); -}; - -/** - * Export constructor. - */ - -module.exports = RefreshTokenGrantType; diff --git a/lib/grant-types/refresh-token-grant-type.ts b/lib/grant-types/refresh-token-grant-type.ts new file mode 100755 index 000000000..827d9a298 --- /dev/null +++ b/lib/grant-types/refresh-token-grant-type.ts @@ -0,0 +1,167 @@ +import { InvalidArgumentError } from '../errors/invalid-argument-error'; +import { InvalidGrantError } from '../errors/invalid-grant-error'; +import { InvalidRequestError } from '../errors/invalid-request-error'; +import { ServerError } from '../errors/server-error'; +import { Client } from '../interfaces/client.interface'; +import { RefreshToken } from '../interfaces/refresh-token.interface'; +import { User } from '../interfaces/user.interface'; +import { Request } from '../request'; +import * as is from '../validator/is'; +import { AbstractGrantType } from './abstract-grant-type'; + +export class RefreshTokenGrantType extends AbstractGrantType { + constructor(options: any = {}) { + super(options); + + if (!options.model) { + throw new InvalidArgumentError('Missing parameter: `model`'); + } + + if (!options.model.getRefreshToken) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `getRefreshToken()`', + ); + } + + if (!options.model.revokeToken) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `revokeToken()`', + ); + } + + if (!options.model.saveToken) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `saveToken()`', + ); + } + } + + /** + * Handle refresh token grant. + * + * @see https://tools.ietf.org/html/rfc6749#section-6 + */ + + async handle(request: Request, client: Client) { + if (!request) { + throw new InvalidArgumentError('Missing parameter: `request`'); + } + + if (!client) { + throw new InvalidArgumentError('Missing parameter: `client`'); + } + + const token = await this.getRefreshToken(request, client); + await this.revokeToken(token); + + return this.saveToken(token.user, client, token.scope); + } + + /** + * Get refresh token. + */ + + async getRefreshToken(request: Request, client: Client) { + if (!request.body.refresh_token) { + throw new InvalidRequestError('Missing parameter: `refresh_token`'); + } + + if (!is.vschar(request.body.refresh_token)) { + throw new InvalidRequestError('Invalid parameter: `refresh_token`'); + } + + const token = await this.model.getRefreshToken(request.body.refresh_token); + + if (!token) { + throw new InvalidGrantError('Invalid grant: refresh token is invalid'); + } + + if (!token.client) { + throw new ServerError( + 'Server error: `getRefreshToken()` did not return a `client` object', + ); + } + + if (!token.user) { + throw new ServerError( + 'Server error: `getRefreshToken()` did not return a `user` object', + ); + } + + if (token.client.id !== client.id) { + throw new InvalidGrantError('Invalid grant: refresh token is invalid'); + } + + if ( + token.refreshTokenExpiresAt && + !(token.refreshTokenExpiresAt instanceof Date) + ) { + throw new ServerError( + 'Server error: `refreshTokenExpiresAt` must be a Date instance', + ); + } + + if ( + token.refreshTokenExpiresAt && + token.refreshTokenExpiresAt < new Date() + ) { + throw new InvalidGrantError('Invalid grant: refresh token has expired'); + } + + return token; + } + + /** + * Revoke the refresh token. + * + * @see https://tools.ietf.org/html/rfc6749#section-6 + */ + + async revokeToken(token: RefreshToken) { + if (this.alwaysIssueNewRefreshToken === false) { + return token; + } + + const status = await this.model.revokeToken(token); + if (!status) { + throw new InvalidGrantError('Invalid grant: refresh token is invalid'); + } + + return token; + } + + /** + * Save token. + */ + + async saveToken(user: User, client: Client, scope: string) { + const fns = [ + this.generateAccessToken(client, user, scope), + this.generateRefreshToken(client, user, scope), + this.getAccessTokenExpiresAt(), + this.getRefreshTokenExpiresAt(), + ]; + + const [ + accessToken, + refreshToken, + accessTokenExpiresAt, + refreshTokenExpiresAt, + ] = await Promise.all(fns as any); + + const token: any = { + accessToken, + accessTokenExpiresAt, + scope, + }; + + if (this.alwaysIssueNewRefreshToken !== false) { + token.refreshToken = refreshToken; + token.refreshTokenExpiresAt = refreshTokenExpiresAt; + } + + const savedToken = await this.model.saveToken(token, client, user); + + return savedToken; + } +} diff --git a/lib/handlers/authenticate-handler.js b/lib/handlers/authenticate-handler.js deleted file mode 100644 index dc9117b27..000000000 --- a/lib/handlers/authenticate-handler.js +++ /dev/null @@ -1,263 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var InvalidArgumentError = require('../errors/invalid-argument-error'); -var InvalidRequestError = require('../errors/invalid-request-error'); -var InsufficientScopeError = require('../errors/insufficient-scope-error'); -var InvalidTokenError = require('../errors/invalid-token-error'); -var OAuthError = require('../errors/oauth-error'); -var Promise = require('bluebird'); -var promisify = require('promisify-any').use(Promise); -var Request = require('../request'); -var Response = require('../response'); -var ServerError = require('../errors/server-error'); -var UnauthorizedRequestError = require('../errors/unauthorized-request-error'); - -/** - * Constructor. - */ - -function AuthenticateHandler(options) { - options = options || {}; - - if (!options.model) { - throw new InvalidArgumentError('Missing parameter: `model`'); - } - - if (!options.model.getAccessToken) { - throw new InvalidArgumentError('Invalid argument: model does not implement `getAccessToken()`'); - } - - if (options.scope && undefined === options.addAcceptedScopesHeader) { - throw new InvalidArgumentError('Missing parameter: `addAcceptedScopesHeader`'); - } - - if (options.scope && undefined === options.addAuthorizedScopesHeader) { - throw new InvalidArgumentError('Missing parameter: `addAuthorizedScopesHeader`'); - } - - if (options.scope && !options.model.verifyScope) { - throw new InvalidArgumentError('Invalid argument: model does not implement `verifyScope()`'); - } - - this.addAcceptedScopesHeader = options.addAcceptedScopesHeader; - this.addAuthorizedScopesHeader = options.addAuthorizedScopesHeader; - this.allowBearerTokensInQueryString = options.allowBearerTokensInQueryString; - this.model = options.model; - this.scope = options.scope; -} - -/** - * Authenticate Handler. - */ - -AuthenticateHandler.prototype.handle = function(request, response) { - if (!(request instanceof Request)) { - throw new InvalidArgumentError('Invalid argument: `request` must be an instance of Request'); - } - - if (!(response instanceof Response)) { - throw new InvalidArgumentError('Invalid argument: `response` must be an instance of Response'); - } - - return Promise.bind(this) - .then(function() { - return this.getTokenFromRequest(request); - }) - .then(function(token) { - return this.getAccessToken(token); - }) - .tap(function(token) { - return this.validateAccessToken(token); - }) - .tap(function(token) { - if (!this.scope) { - return; - } - - return this.verifyScope(token); - }) - .tap(function(token) { - return this.updateResponse(response, token); - }) - .catch(function(e) { - // Include the "WWW-Authenticate" response header field if the client - // lacks any authentication information. - // - // @see https://tools.ietf.org/html/rfc6750#section-3.1 - if (e instanceof UnauthorizedRequestError) { - response.set('WWW-Authenticate', 'Bearer realm="Service"'); - } - - if (!(e instanceof OAuthError)) { - throw new ServerError(e); - } - - throw e; - }); -}; - -/** - * Get the token from the header or body, depending on the request. - * - * "Clients MUST NOT use more than one method to transmit the token in each request." - * - * @see https://tools.ietf.org/html/rfc6750#section-2 - */ - -AuthenticateHandler.prototype.getTokenFromRequest = function(request) { - var headerToken = request.get('Authorization'); - var queryToken = request.query.access_token; - var bodyToken = request.body.access_token; - - if (!!headerToken + !!queryToken + !!bodyToken > 1) { - throw new InvalidRequestError('Invalid request: only one authentication method is allowed'); - } - - if (headerToken) { - return this.getTokenFromRequestHeader(request); - } - - if (queryToken) { - return this.getTokenFromRequestQuery(request); - } - - if (bodyToken) { - return this.getTokenFromRequestBody(request); - } - - throw new UnauthorizedRequestError('Unauthorized request: no authentication given'); -}; - -/** - * Get the token from the request header. - * - * @see http://tools.ietf.org/html/rfc6750#section-2.1 - */ - -AuthenticateHandler.prototype.getTokenFromRequestHeader = function(request) { - var token = request.get('Authorization'); - var matches = token.match(/Bearer\s(\S+)/); - - if (!matches) { - throw new InvalidRequestError('Invalid request: malformed authorization header'); - } - - return matches[1]; -}; - -/** - * Get the token from the request query. - * - * "Don't pass bearer tokens in page URLs: Bearer tokens SHOULD NOT be passed in page - * URLs (for example, as query string parameters). Instead, bearer tokens SHOULD be - * passed in HTTP message headers or message bodies for which confidentiality measures - * are taken. Browsers, web servers, and other software may not adequately secure URLs - * in the browser history, web server logs, and other data structures. If bearer tokens - * are passed in page URLs, attackers might be able to steal them from the history data, - * logs, or other unsecured locations." - * - * @see http://tools.ietf.org/html/rfc6750#section-2.3 - */ - -AuthenticateHandler.prototype.getTokenFromRequestQuery = function(request) { - if (!this.allowBearerTokensInQueryString) { - throw new InvalidRequestError('Invalid request: do not send bearer tokens in query URLs'); - } - - return request.query.access_token; -}; - -/** - * Get the token from the request body. - * - * "The HTTP request method is one for which the request-body has defined semantics. - * In particular, this means that the "GET" method MUST NOT be used." - * - * @see http://tools.ietf.org/html/rfc6750#section-2.2 - */ - -AuthenticateHandler.prototype.getTokenFromRequestBody = function(request) { - if (request.method === 'GET') { - throw new InvalidRequestError('Invalid request: token may not be passed in the body when using the GET verb'); - } - - if (!request.is('application/x-www-form-urlencoded')) { - throw new InvalidRequestError('Invalid request: content must be application/x-www-form-urlencoded'); - } - - return request.body.access_token; -}; - -/** - * Get the access token from the model. - */ - -AuthenticateHandler.prototype.getAccessToken = function(token) { - return promisify(this.model.getAccessToken, 1).call(this.model, token) - .then(function(accessToken) { - if (!accessToken) { - throw new InvalidTokenError('Invalid token: access token is invalid'); - } - - if (!accessToken.user) { - throw new ServerError('Server error: `getAccessToken()` did not return a `user` object'); - } - - return accessToken; - }); -}; - -/** - * Validate access token. - */ - -AuthenticateHandler.prototype.validateAccessToken = function(accessToken) { - if (!(accessToken.accessTokenExpiresAt instanceof Date)) { - throw new ServerError('Server error: `accessTokenExpiresAt` must be a Date instance'); - } - - if (accessToken.accessTokenExpiresAt < new Date()) { - throw new InvalidTokenError('Invalid token: access token has expired'); - } - - return accessToken; -}; - -/** - * Verify scope. - */ - -AuthenticateHandler.prototype.verifyScope = function(accessToken) { - return promisify(this.model.verifyScope, 2).call(this.model, accessToken, this.scope) - .then(function(scope) { - if (!scope) { - throw new InsufficientScopeError('Insufficient scope: authorized scope is insufficient'); - } - - return scope; - }); -}; - -/** - * Update response. - */ - -AuthenticateHandler.prototype.updateResponse = function(response, accessToken) { - if (this.scope && this.addAcceptedScopesHeader) { - response.set('X-Accepted-OAuth-Scopes', this.scope); - } - - if (this.scope && this.addAuthorizedScopesHeader) { - response.set('X-OAuth-Scopes', accessToken.scope); - } -}; - -/** - * Export constructor. - */ - -module.exports = AuthenticateHandler; diff --git a/lib/handlers/authenticate-handler.ts b/lib/handlers/authenticate-handler.ts new file mode 100755 index 000000000..d59b8ff27 --- /dev/null +++ b/lib/handlers/authenticate-handler.ts @@ -0,0 +1,268 @@ +import { InsufficientScopeError } from '../errors/insufficient-scope-error'; +import { InvalidArgumentError } from '../errors/invalid-argument-error'; +import { InvalidRequestError } from '../errors/invalid-request-error'; +import { InvalidTokenError } from '../errors/invalid-token-error'; +import { OAuthError } from '../errors/oauth-error'; +import { ServerError } from '../errors/server-error'; +import { UnauthorizedRequestError } from '../errors/unauthorized-request-error'; +import { Model } from '../interfaces/model.interface'; +import { Token } from '../interfaces/token.interface'; +import { Request } from '../request'; +import { Response } from '../response'; + +export class AuthenticateHandler { + addAcceptedScopesHeader: any; + addAuthorizedScopesHeader: any; + allowBearerTokensInQueryString: any; + model: Model; + scope: any; + constructor(options: any = {}) { + if (!options.model) { + throw new InvalidArgumentError('Missing parameter: `model`'); + } + + if (!options.model.getAccessToken) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `getAccessToken()`', + ); + } + + if (options.scope && options.addAcceptedScopesHeader === undefined) { + throw new InvalidArgumentError( + 'Missing parameter: `addAcceptedScopesHeader`', + ); + } + + if (options.scope && options.addAuthorizedScopesHeader === undefined) { + throw new InvalidArgumentError( + 'Missing parameter: `addAuthorizedScopesHeader`', + ); + } + + if (options.scope && !options.model.verifyScope) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `verifyScope()`', + ); + } + + this.addAcceptedScopesHeader = options.addAcceptedScopesHeader; + this.addAuthorizedScopesHeader = options.addAuthorizedScopesHeader; + this.allowBearerTokensInQueryString = + options.allowBearerTokensInQueryString; + this.model = options.model; + this.scope = options.scope; + } + + /** + * Authenticate Handler. + */ + + async handle(request: Request, response: Response) { + if (!(request instanceof Request)) { + throw new InvalidArgumentError( + 'Invalid argument: `request` must be an instance of Request', + ); + } + + if (!(response instanceof Response)) { + throw new InvalidArgumentError( + 'Invalid argument: `response` must be an instance of Response', + ); + } + try { + let token = await this.getTokenFromRequest(request); + token = await this.getAccessToken(token); + await this.validateAccessToken(token); + if (this.scope) { + await this.verifyScope(token); + } + await this.updateResponse(response, token); + + return token; + } catch (e) { + // Include the "WWW-Authenticate" response header field if the client + // lacks any authentication information. + // + // @see https://tools.ietf.org/html/rfc6750#section-3.1 + if (e instanceof UnauthorizedRequestError) { + response.set('WWW-Authenticate', 'Bearer realm="Service"'); + } + + if (!(e instanceof OAuthError)) { + throw new ServerError(e); + } + + throw e; + } + } + + /** + * Get the token from the header or body, depending on the request. + * + * "Clients MUST NOT use more than one method to transmit the token in each request." + * + * @see https://tools.ietf.org/html/rfc6750#section-2 + */ + + getTokenFromRequest(request: Request) { + const headerToken = request.get('Authorization'); + const queryToken = request.query.access_token; + const bodyToken = request.body.access_token; + + if ((!!headerToken && 1) + (!!queryToken && 1) + (!!bodyToken && 1) > 1) { + throw new InvalidRequestError( + 'Invalid request: only one authentication method is allowed', + ); + } + + if (headerToken) { + return this.getTokenFromRequestHeader(request); + } + + if (queryToken) { + return this.getTokenFromRequestQuery(request); + } + + if (bodyToken) { + return this.getTokenFromRequestBody(request); + } + + throw new UnauthorizedRequestError( + 'Unauthorized request: no authentication given', + ); + } + + /** + * Get the token from the request header. + * + * @see http://tools.ietf.org/html/rfc6750#section-2.1 + */ + + getTokenFromRequestHeader = (request: Request) => { + const token = request.get('Authorization'); + const matches = token.match(/Bearer\s(\S+)/); + + if (!matches) { + throw new InvalidRequestError( + 'Invalid request: malformed authorization header', + ); + } + + return matches[1]; + }; + + /** + * Get the token from the request query. + * + * "Don't pass bearer tokens in page URLs: Bearer tokens SHOULD NOT be passed in page + * URLs (for example, as query string parameters). Instead, bearer tokens SHOULD be + * passed in HTTP message headers or message bodies for which confidentiality measures + * are taken. Browsers, web servers, and other software may not adequately secure URLs + * in the browser history, web server logs, and other data structures. If bearer tokens + * are passed in page URLs, attackers might be able to steal them from the history data, + * logs, or other unsecured locations." + * + * @see http://tools.ietf.org/html/rfc6750#section-2.3 + */ + + getTokenFromRequestQuery(request: Request) { + if (!this.allowBearerTokensInQueryString) { + throw new InvalidRequestError( + 'Invalid request: do not send bearer tokens in query URLs', + ); + } + + return request.query.access_token; + } + + /** + * Get the token from the request body. + * + * "The HTTP request method is one for which the request-body has defined semantics. + * In particular, this means that the "GET" method MUST NOT be used." + * + * @see http://tools.ietf.org/html/rfc6750#section-2.2 + */ + + getTokenFromRequestBody = (request: Request) => { + if (request.method === 'GET') { + throw new InvalidRequestError( + 'Invalid request: token may not be passed in the body when using the GET verb', + ); + } + + if (!request.is('application/x-www-form-urlencoded')) { + throw new InvalidRequestError( + 'Invalid request: content must be application/x-www-form-urlencoded', + ); + } + + return request.body.access_token; + }; + + /** + * Get the access token from the model. + */ + + async getAccessToken(token: string) { + const accessToken = await this.model.getAccessToken(token); + if (!accessToken) { + throw new InvalidTokenError('Invalid token: access token is invalid'); + } + + if (!accessToken.user) { + throw new ServerError( + 'Server error: `getAccessToken()` did not return a `user` object', + ); + } + + return accessToken; + } + + /** + * Validate access token. + */ + + validateAccessToken = (accessToken: Token) => { + if (!(accessToken.accessTokenExpiresAt instanceof Date)) { + throw new ServerError( + 'Server error: `accessTokenExpiresAt` must be a Date instance', + ); + } + + if (accessToken.accessTokenExpiresAt < new Date()) { + throw new InvalidTokenError('Invalid token: access token has expired'); + } + + return accessToken; + }; + + /** + * Verify scope. + */ + + async verifyScope(accessToken: Token) { + const scope = await this.model.verifyScope(accessToken, this.scope); + if (!scope) { + throw new InsufficientScopeError( + 'Insufficient scope: authorized scope is insufficient', + ); + } + + return scope; + } + + /** + * Update response. + */ + + updateResponse(response: Response, accessToken) { + if (this.scope && this.addAcceptedScopesHeader) { + response.set('X-Accepted-OAuth-Scopes', this.scope); + } + + if (this.scope && this.addAuthorizedScopesHeader) { + response.set('X-OAuth-Scopes', accessToken.scope); + } + } +} diff --git a/lib/handlers/authorize-handler.js b/lib/handlers/authorize-handler.js deleted file mode 100644 index 984136a8d..000000000 --- a/lib/handlers/authorize-handler.js +++ /dev/null @@ -1,332 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var _ = require('lodash'); -var AccessDeniedError = require('../errors/access-denied-error'); -var AuthenticateHandler = require('../handlers/authenticate-handler'); -var InvalidArgumentError = require('../errors/invalid-argument-error'); -var InvalidClientError = require('../errors/invalid-client-error'); -var InvalidRequestError = require('../errors/invalid-request-error'); -var InvalidScopeError = require('../errors/invalid-scope-error'); -var UnsupportedResponseTypeError = require('../errors/unsupported-response-type-error'); -var OAuthError = require('../errors/oauth-error'); -var Promise = require('bluebird'); -var promisify = require('promisify-any').use(Promise); -var Request = require('../request'); -var Response = require('../response'); -var ServerError = require('../errors/server-error'); -var UnauthorizedClientError = require('../errors/unauthorized-client-error'); -var is = require('../validator/is'); -var tokenUtil = require('../utils/token-util'); -var url = require('url'); - -/** - * Response types. - */ - -var responseTypes = { - code: require('../response-types/code-response-type'), - //token: require('../response-types/token-response-type') -}; - -/** - * Constructor. - */ - -function AuthorizeHandler(options) { - options = options || {}; - - if (options.authenticateHandler && !options.authenticateHandler.handle) { - throw new InvalidArgumentError('Invalid argument: authenticateHandler does not implement `handle()`'); - } - - if (!options.authorizationCodeLifetime) { - throw new InvalidArgumentError('Missing parameter: `authorizationCodeLifetime`'); - } - - if (!options.model) { - throw new InvalidArgumentError('Missing parameter: `model`'); - } - - if (!options.model.getClient) { - throw new InvalidArgumentError('Invalid argument: model does not implement `getClient()`'); - } - - if (!options.model.saveAuthorizationCode) { - throw new InvalidArgumentError('Invalid argument: model does not implement `saveAuthorizationCode()`'); - } - - this.allowEmptyState = options.allowEmptyState; - this.authenticateHandler = options.authenticateHandler || new AuthenticateHandler(options); - this.authorizationCodeLifetime = options.authorizationCodeLifetime; - this.model = options.model; -} - -/** - * Authorize Handler. - */ - -AuthorizeHandler.prototype.handle = function(request, response) { - if (!(request instanceof Request)) { - throw new InvalidArgumentError('Invalid argument: `request` must be an instance of Request'); - } - - if (!(response instanceof Response)) { - throw new InvalidArgumentError('Invalid argument: `response` must be an instance of Response'); - } - - if ('false' === request.query.allowed) { - return Promise.reject(new AccessDeniedError('Access denied: user denied access to application')); - } - - var fns = [ - this.getAuthorizationCodeLifetime(), - this.getClient(request), - this.getUser(request, response) - ]; - - return Promise.all(fns) - .bind(this) - .spread(function(expiresAt, client, user) { - var uri = this.getRedirectUri(request, client); - var scope; - var state; - var ResponseType; - - return Promise.bind(this) - .then(function() { - scope = this.getScope(request); - - return this.generateAuthorizationCode(client, user, scope); - }) - .then(function(authorizationCode) { - state = this.getState(request); - ResponseType = this.getResponseType(request); - - return this.saveAuthorizationCode(authorizationCode, expiresAt, scope, client, uri, user); - }) - .then(function(code) { - var responseType = new ResponseType(code.authorizationCode); - var redirectUri = this.buildSuccessRedirectUri(uri, responseType); - - this.updateResponse(response, redirectUri, state); - - return code; - }) - .catch(function(e) { - if (!(e instanceof OAuthError)) { - e = new ServerError(e); - } - var redirectUri = this.buildErrorRedirectUri(uri, e); - - this.updateResponse(response, redirectUri, state); - - throw e; - }); - }); -}; - -/** - * Generate authorization code. - */ - -AuthorizeHandler.prototype.generateAuthorizationCode = function(client, user, scope) { - if (this.model.generateAuthorizationCode) { - return promisify(this.model.generateAuthorizationCode).call(this.model, client, user, scope); - } - return tokenUtil.generateRandomToken(); -}; - -/** - * Get authorization code lifetime. - */ - -AuthorizeHandler.prototype.getAuthorizationCodeLifetime = function() { - var expires = new Date(); - - expires.setSeconds(expires.getSeconds() + this.authorizationCodeLifetime); - return expires; -}; - -/** - * Get the client from the model. - */ - -AuthorizeHandler.prototype.getClient = function(request) { - var clientId = request.body.client_id || request.query.client_id; - - if (!clientId) { - throw new InvalidRequestError('Missing parameter: `client_id`'); - } - - if (!is.vschar(clientId)) { - throw new InvalidRequestError('Invalid parameter: `client_id`'); - } - - var redirectUri = request.body.redirect_uri || request.query.redirect_uri; - - if (redirectUri && !is.uri(redirectUri)) { - throw new InvalidRequestError('Invalid request: `redirect_uri` is not a valid URI'); - } - return promisify(this.model.getClient, 2).call(this.model, clientId, null) - .then(function(client) { - if (!client) { - throw new InvalidClientError('Invalid client: client credentials are invalid'); - } - - if (!client.grants) { - throw new InvalidClientError('Invalid client: missing client `grants`'); - } - - if (!_.includes(client.grants, 'authorization_code')) { - throw new UnauthorizedClientError('Unauthorized client: `grant_type` is invalid'); - } - - if (!client.redirectUris || 0 === client.redirectUris.length) { - throw new InvalidClientError('Invalid client: missing client `redirectUri`'); - } - - if (redirectUri && !_.includes(client.redirectUris, redirectUri)) { - throw new InvalidClientError('Invalid client: `redirect_uri` does not match client value'); - } - return client; - }); -}; - -/** - * Get scope from the request. - */ - -AuthorizeHandler.prototype.getScope = function(request) { - var scope = request.body.scope || request.query.scope; - - if (!is.nqschar(scope)) { - throw new InvalidScopeError('Invalid parameter: `scope`'); - } - - return scope; -}; - -/** - * Get state from the request. - */ - -AuthorizeHandler.prototype.getState = function(request) { - var state = request.body.state || request.query.state; - - if (!this.allowEmptyState && !state) { - throw new InvalidRequestError('Missing parameter: `state`'); - } - - if (!is.vschar(state)) { - throw new InvalidRequestError('Invalid parameter: `state`'); - } - - return state; -}; - -/** - * Get user by calling the authenticate middleware. - */ - -AuthorizeHandler.prototype.getUser = function(request, response) { - if (this.authenticateHandler instanceof AuthenticateHandler) { - return this.authenticateHandler.handle(request, response).get('user'); - } - return promisify(this.authenticateHandler.handle, 2)(request, response).then(function(user) { - if (!user) { - throw new ServerError('Server error: `handle()` did not return a `user` object'); - } - - return user; - }); -}; - -/** - * Get redirect URI. - */ - -AuthorizeHandler.prototype.getRedirectUri = function(request, client) { - return request.body.redirect_uri || request.query.redirect_uri || client.redirectUris[0]; -}; - -/** - * Save authorization code. - */ - -AuthorizeHandler.prototype.saveAuthorizationCode = function(authorizationCode, expiresAt, scope, client, redirectUri, user) { - var code = { - authorizationCode: authorizationCode, - expiresAt: expiresAt, - redirectUri: redirectUri, - scope: scope - }; - return promisify(this.model.saveAuthorizationCode, 3).call(this.model, code, client, user); -}; - -/** - * Get response type. - */ - -AuthorizeHandler.prototype.getResponseType = function(request) { - var responseType = request.body.response_type || request.query.response_type; - - if (!responseType) { - throw new InvalidRequestError('Missing parameter: `response_type`'); - } - - if (!_.has(responseTypes, responseType)) { - throw new UnsupportedResponseTypeError('Unsupported response type: `response_type` is not supported'); - } - - return responseTypes[responseType]; -}; - -/** - * Build a successful response that redirects the user-agent to the client-provided url. - */ - -AuthorizeHandler.prototype.buildSuccessRedirectUri = function(redirectUri, responseType) { - return responseType.buildRedirectUri(redirectUri); -}; - -/** - * Build an error response that redirects the user-agent to the client-provided url. - */ - -AuthorizeHandler.prototype.buildErrorRedirectUri = function(redirectUri, error) { - var uri = url.parse(redirectUri); - - uri.query = { - error: error.name - }; - - if (error.message) { - uri.query.error_description = error.message; - } - - return uri; -}; - -/** - * Update response with the redirect uri and the state parameter, if available. - */ - -AuthorizeHandler.prototype.updateResponse = function(response, redirectUri, state) { - redirectUri.query = redirectUri.query || {}; - - if (state) { - redirectUri.query.state = state; - } - - response.redirect(url.format(redirectUri)); -}; - -/** - * Export constructor. - */ - -module.exports = AuthorizeHandler; diff --git a/lib/handlers/authorize-handler.ts b/lib/handlers/authorize-handler.ts new file mode 100755 index 000000000..454482696 --- /dev/null +++ b/lib/handlers/authorize-handler.ts @@ -0,0 +1,369 @@ +import { has } from 'lodash'; +import { format, parse, UrlWithParsedQuery } from 'url'; +import { AccessDeniedError } from '../errors/access-denied-error'; +import { InvalidArgumentError } from '../errors/invalid-argument-error'; +import { InvalidClientError } from '../errors/invalid-client-error'; +import { InvalidRequestError } from '../errors/invalid-request-error'; +import { InvalidScopeError } from '../errors/invalid-scope-error'; +import { OAuthError } from '../errors/oauth-error'; +import { ServerError } from '../errors/server-error'; +import { UnauthorizedClientError } from '../errors/unauthorized-client-error'; +import { UnsupportedResponseTypeError } from '../errors/unsupported-response-type-error'; +import { AuthenticateHandler } from '../handlers/authenticate-handler'; +import { AuthorizationCode } from '../interfaces/authorization-code.interface'; +import { Client } from '../interfaces/client.interface'; +import { Model } from '../interfaces/model.interface'; +import { User } from '../interfaces/user.interface'; +import { Request } from '../request'; +import { Response } from '../response'; +import { CodeResponseType } from '../response-types/code-response-type'; +import * as tokenUtil from '../utils/token-util'; +import * as is from '../validator/is'; + +const responseTypes = { + code: CodeResponseType, + // token: require('../response-types/token-response-type') +}; + +export class AuthorizeHandler { + allowEmptyState: boolean; + authenticateHandler: any; + authorizationCodeLifetime: number; + model: Model; + constructor(options: any = {}) { + if (options.authenticateHandler && !options.authenticateHandler.handle) { + throw new InvalidArgumentError( + 'Invalid argument: authenticateHandler does not implement `handle()`', + ); + } + + if (!options.authorizationCodeLifetime) { + throw new InvalidArgumentError( + 'Missing parameter: `authorizationCodeLifetime`', + ); + } + + if (!options.model) { + throw new InvalidArgumentError('Missing parameter: `model`'); + } + + if (!options.model.getClient) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `getClient()`', + ); + } + + if (!options.model.saveAuthorizationCode) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `saveAuthorizationCode()`', + ); + } + + this.allowEmptyState = options.allowEmptyState; + this.authenticateHandler = + options.authenticateHandler || new AuthenticateHandler(options); + this.authorizationCodeLifetime = options.authorizationCodeLifetime; + this.model = options.model; + } + + /** + * Authorize Handler. + */ + + async handle(request: Request, response: Response) { + if (!(request instanceof Request)) { + throw new InvalidArgumentError( + 'Invalid argument: `request` must be an instance of Request', + ); + } + + if (!(response instanceof Response)) { + throw new InvalidArgumentError( + 'Invalid argument: `response` must be an instance of Response', + ); + } + + if (request.query.allowed === 'false') { + throw new AccessDeniedError( + 'Access denied: user denied access to application', + ); + } + + const fns = [ + this.getAuthorizationCodeLifetime(), + this.getClient(request), + this.getUser(request, response), + ]; + + const [expiresAt, client, user] = await Promise.all(fns); + const uri = this.getRedirectUri(request, client); + let scope: any; + let state: any; + let ResponseType: any; + + try { + scope = this.getScope(request); + const authorizationCode = await this.generateAuthorizationCode( + client, + user, + scope, + ); + state = this.getState(request); + ResponseType = this.getResponseType(request); + const code = await this.saveAuthorizationCode( + authorizationCode, + expiresAt, + scope, + client, + uri, + user, + ); + const responseType = new ResponseType(code.authorizationCode); + const redirectUri = this.buildSuccessRedirectUri(uri, responseType); + this.updateResponse(response, redirectUri, state); + + return code; + } catch (e) { + if (!(e instanceof OAuthError)) { + e = new ServerError(e); + } + const redirectUri = this.buildErrorRedirectUri(uri, e); + this.updateResponse(response, redirectUri, state); + throw e; + } + } + + /** + * Generate authorization code. + */ + + generateAuthorizationCode(client, user, scope) { + if (this.model.generateAuthorizationCode) { + return this.model.generateAuthorizationCode(client, user, scope); + } + + return tokenUtil.GenerateRandomToken(); + } + + /** + * Get authorization code lifetime. + */ + + getAuthorizationCodeLifetime() { + const expires = new Date(); + + expires.setSeconds(expires.getSeconds() + this.authorizationCodeLifetime); + + return expires; + } + + /** + * Get the client from the model. + */ + + async getClient(request: Request) { + const clientId = request.body.client_id || request.query.client_id; + + if (!clientId) { + throw new InvalidRequestError('Missing parameter: `client_id`'); + } + + if (!is.vschar(clientId)) { + throw new InvalidRequestError('Invalid parameter: `client_id`'); + } + + const redirectUri = request.body.redirect_uri || request.query.redirect_uri; + + if (redirectUri && !is.uri(redirectUri)) { + throw new InvalidRequestError( + 'Invalid request: `redirect_uri` is not a valid URI', + ); + } + + const client = await this.model.getClient(clientId); + if (!client) { + throw new InvalidClientError( + 'Invalid client: client credentials are invalid', + ); + } + + if (!client.grants) { + throw new InvalidClientError('Invalid client: missing client `grants`'); + } + + if (!client.grants.includes('authorization_code')) { + throw new UnauthorizedClientError( + 'Unauthorized client: `grant_type` is invalid', + ); + } + + if (!client.redirectUris || client.redirectUris.length === 0) { + throw new InvalidClientError( + 'Invalid client: missing client `redirectUri`', + ); + } + + if (redirectUri && !client.redirectUris.includes(redirectUri)) { + throw new InvalidClientError( + 'Invalid client: `redirect_uri` does not match client value', + ); + } + + return client; + } + + /** + * Get scope from the request. + */ + + getScope = (request: Request) => { + const scope = request.body.scope || request.query.scope; + + if (!is.nqschar(scope)) { + throw new InvalidScopeError('Invalid parameter: `scope`'); + } + + return scope; + }; + + /** + * Get state from the request. + */ + + getState(request) { + const state = request.body.state || request.query.state; + + if (!this.allowEmptyState && !state) { + throw new InvalidRequestError('Missing parameter: `state`'); + } + + if (!is.vschar(state)) { + throw new InvalidRequestError('Invalid parameter: `state`'); + } + + return state; + } + + /** + * Get user by calling the authenticate middleware. + */ + + async getUser(request: Request, response: Response) { + if (this.authenticateHandler instanceof AuthenticateHandler) { + const data = await this.authenticateHandler.handle(request, response); + + return data.user; + } + + const user = await this.authenticateHandler.handle(request, response); + if (!user) { + throw new ServerError( + 'Server error: `handle()` did not return a `user` object', + ); + } + + return user; + } + + /** + * Get redirect URI. + */ + + getRedirectUri = (request: Request, client) => { + return ( + request.body.redirect_uri || + request.query.redirect_uri || + client.redirectUris[0] + ); + }; + + /** + * Save authorization code. + */ + + async saveAuthorizationCode( + authorizationCode: string, + expiresAt: Date, + scope: string, + client: Client, + redirectUri: string, + user: User, + ) { + const code = { + authorizationCode, + expiresAt, + redirectUri, + scope, + } as AuthorizationCode; + + return this.model.saveAuthorizationCode(code, client, user); + } + + /** + * Get response type. + */ + + getResponseType = (request: Request) => { + const responseType = + request.body.response_type || request.query.response_type; + + if (!responseType) { + throw new InvalidRequestError('Missing parameter: `response_type`'); + } + + if (!has(responseTypes, responseType)) { + throw new UnsupportedResponseTypeError( + 'Unsupported response type: `response_type` is not supported', + ); + } + + return responseTypes[responseType]; + }; + + /** + * Build a successful response that redirects the user-agent to the client-provided url. + */ + + buildSuccessRedirectUri = ( + redirectUri: string, + responseType: CodeResponseType, + ) => { + return responseType.buildRedirectUri(redirectUri); + }; + + /** + * Build an error response that redirects the user-agent to the client-provided url. + */ + + buildErrorRedirectUri = (redirectUri: string, error: Error) => { + const uri = parse(redirectUri, true); + + uri.query = { + error: error.name, + }; + + if (error.message) { + uri.query.error_description = error.message; + } + + return uri; + }; + + /** + * Update response with the redirect uri and the state parameter, if available. + */ + + updateResponse = ( + response: Response, + redirectUri: UrlWithParsedQuery, + state: string, + ) => { + redirectUri.query = redirectUri.query || {}; + + if (state) { + redirectUri.query.state = state; + } + + response.redirect(format(redirectUri)); + }; +} diff --git a/lib/handlers/token-handler.js b/lib/handlers/token-handler.js deleted file mode 100644 index feaad3f54..000000000 --- a/lib/handlers/token-handler.js +++ /dev/null @@ -1,297 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var _ = require('lodash'); -var BearerTokenType = require('../token-types/bearer-token-type'); -var InvalidArgumentError = require('../errors/invalid-argument-error'); -var InvalidClientError = require('../errors/invalid-client-error'); -var InvalidRequestError = require('../errors/invalid-request-error'); -var OAuthError = require('../errors/oauth-error'); -var Promise = require('bluebird'); -var promisify = require('promisify-any').use(Promise); -var Request = require('../request'); -var Response = require('../response'); -var ServerError = require('../errors/server-error'); -var TokenModel = require('../models/token-model'); -var UnauthorizedClientError = require('../errors/unauthorized-client-error'); -var UnsupportedGrantTypeError = require('../errors/unsupported-grant-type-error'); -var auth = require('basic-auth'); -var is = require('../validator/is'); - -/** - * Grant types. - */ - -var grantTypes = { - authorization_code: require('../grant-types/authorization-code-grant-type'), - client_credentials: require('../grant-types/client-credentials-grant-type'), - password: require('../grant-types/password-grant-type'), - refresh_token: require('../grant-types/refresh-token-grant-type') -}; - -/** - * Constructor. - */ - -function TokenHandler(options) { - options = options || {}; - - if (!options.accessTokenLifetime) { - throw new InvalidArgumentError('Missing parameter: `accessTokenLifetime`'); - } - - if (!options.model) { - throw new InvalidArgumentError('Missing parameter: `model`'); - } - - if (!options.refreshTokenLifetime) { - throw new InvalidArgumentError('Missing parameter: `refreshTokenLifetime`'); - } - - if (!options.model.getClient) { - throw new InvalidArgumentError('Invalid argument: model does not implement `getClient()`'); - } - - this.accessTokenLifetime = options.accessTokenLifetime; - this.grantTypes = _.assign({}, grantTypes, options.extendedGrantTypes); - this.model = options.model; - this.refreshTokenLifetime = options.refreshTokenLifetime; - this.allowExtendedTokenAttributes = options.allowExtendedTokenAttributes; - this.requireClientAuthentication = options.requireClientAuthentication || {}; - this.alwaysIssueNewRefreshToken = options.alwaysIssueNewRefreshToken !== false; -} - -/** - * Token Handler. - */ - -TokenHandler.prototype.handle = function(request, response) { - if (!(request instanceof Request)) { - throw new InvalidArgumentError('Invalid argument: `request` must be an instance of Request'); - } - - if (!(response instanceof Response)) { - throw new InvalidArgumentError('Invalid argument: `response` must be an instance of Response'); - } - - if (request.method !== 'POST') { - return Promise.reject(new InvalidRequestError('Invalid request: method must be POST')); - } - - if (!request.is('application/x-www-form-urlencoded')) { - return Promise.reject(new InvalidRequestError('Invalid request: content must be application/x-www-form-urlencoded')); - } - - return Promise.bind(this) - .then(function() { - return this.getClient(request, response); - }) - .then(function(client) { - return this.handleGrantType(request, client); - }) - .tap(function(data) { - var model = new TokenModel(data, {allowExtendedTokenAttributes: this.allowExtendedTokenAttributes}); - var tokenType = this.getTokenType(model); - - this.updateSuccessResponse(response, tokenType); - }).catch(function(e) { - if (!(e instanceof OAuthError)) { - e = new ServerError(e); - } - - this.updateErrorResponse(response, e); - - throw e; - }); -}; - -/** - * Get the client from the model. - */ - -TokenHandler.prototype.getClient = function(request, response) { - var credentials = this.getClientCredentials(request); - var grantType = request.body.grant_type; - - if (!credentials.clientId) { - throw new InvalidRequestError('Missing parameter: `client_id`'); - } - - if (this.isClientAuthenticationRequired(grantType) && !credentials.clientSecret) { - throw new InvalidRequestError('Missing parameter: `client_secret`'); - } - - if (!is.vschar(credentials.clientId)) { - throw new InvalidRequestError('Invalid parameter: `client_id`'); - } - - if (credentials.clientSecret && !is.vschar(credentials.clientSecret)) { - throw new InvalidRequestError('Invalid parameter: `client_secret`'); - } - - return promisify(this.model.getClient, 2).call(this.model, credentials.clientId, credentials.clientSecret) - .then(function(client) { - if (!client) { - throw new InvalidClientError('Invalid client: client is invalid'); - } - - if (!client.grants) { - throw new ServerError('Server error: missing client `grants`'); - } - - if (!(client.grants instanceof Array)) { - throw new ServerError('Server error: `grants` must be an array'); - } - - return client; - }) - .catch(function(e) { - // Include the "WWW-Authenticate" response header field if the client - // attempted to authenticate via the "Authorization" request header. - // - // @see https://tools.ietf.org/html/rfc6749#section-5.2. - if ((e instanceof InvalidClientError) && request.get('authorization')) { - response.set('WWW-Authenticate', 'Basic realm="Service"'); - - throw new InvalidClientError(e, { code: 401 }); - } - - throw e; - }); -}; - -/** - * Get client credentials. - * - * The client credentials may be sent using the HTTP Basic authentication scheme or, alternatively, - * the `client_id` and `client_secret` can be embedded in the body. - * - * @see https://tools.ietf.org/html/rfc6749#section-2.3.1 - */ - -TokenHandler.prototype.getClientCredentials = function(request) { - var credentials = auth(request); - var grantType = request.body.grant_type; - - if (credentials) { - return { clientId: credentials.name, clientSecret: credentials.pass }; - } - - if (request.body.client_id && request.body.client_secret) { - return { clientId: request.body.client_id, clientSecret: request.body.client_secret }; - } - - if (!this.isClientAuthenticationRequired(grantType)) { - if(request.body.client_id) { - return { clientId: request.body.client_id }; - } - } - - throw new InvalidClientError('Invalid client: cannot retrieve client credentials'); -}; - -/** - * Handle grant type. - */ - -TokenHandler.prototype.handleGrantType = function(request, client) { - var grantType = request.body.grant_type; - - if (!grantType) { - throw new InvalidRequestError('Missing parameter: `grant_type`'); - } - - if (!is.nchar(grantType) && !is.uri(grantType)) { - throw new InvalidRequestError('Invalid parameter: `grant_type`'); - } - - if (!_.has(this.grantTypes, grantType)) { - throw new UnsupportedGrantTypeError('Unsupported grant type: `grant_type` is invalid'); - } - - if (!_.includes(client.grants, grantType)) { - throw new UnauthorizedClientError('Unauthorized client: `grant_type` is invalid'); - } - - var accessTokenLifetime = this.getAccessTokenLifetime(client); - var refreshTokenLifetime = this.getRefreshTokenLifetime(client); - var Type = this.grantTypes[grantType]; - - var options = { - accessTokenLifetime: accessTokenLifetime, - model: this.model, - refreshTokenLifetime: refreshTokenLifetime, - alwaysIssueNewRefreshToken: this.alwaysIssueNewRefreshToken - }; - - return new Type(options) - .handle(request, client); -}; - -/** - * Get access token lifetime. - */ - -TokenHandler.prototype.getAccessTokenLifetime = function(client) { - return client.accessTokenLifetime || this.accessTokenLifetime; -}; - -/** - * Get refresh token lifetime. - */ - -TokenHandler.prototype.getRefreshTokenLifetime = function(client) { - return client.refreshTokenLifetime || this.refreshTokenLifetime; -}; - -/** - * Get token type. - */ - -TokenHandler.prototype.getTokenType = function(model) { - return new BearerTokenType(model.accessToken, model.accessTokenLifetime, model.refreshToken, model.scope, model.customAttributes); -}; - -/** - * Update response when a token is generated. - */ - -TokenHandler.prototype.updateSuccessResponse = function(response, tokenType) { - response.body = tokenType.valueOf(); - - response.set('Cache-Control', 'no-store'); - response.set('Pragma', 'no-cache'); -}; - -/** - * Update response when an error is thrown. - */ - -TokenHandler.prototype.updateErrorResponse = function(response, error) { - response.body = { - error: error.name, - error_description: error.message - }; - - response.status = error.code; -}; - -/** - * Given a grant type, check if client authentication is required - */ -TokenHandler.prototype.isClientAuthenticationRequired = function(grantType) { - if (Object.keys(this.requireClientAuthentication).length > 0) { - return (typeof this.requireClientAuthentication[grantType] !== 'undefined') ? this.requireClientAuthentication[grantType] : true; - } else { - return true; - } -}; - -/** - * Export constructor. - */ - -module.exports = TokenHandler; diff --git a/lib/handlers/token-handler.ts b/lib/handlers/token-handler.ts new file mode 100755 index 000000000..d51b36c7d --- /dev/null +++ b/lib/handlers/token-handler.ts @@ -0,0 +1,323 @@ +import * as auth from 'basic-auth'; +import { has } from 'lodash'; +import { InvalidArgumentError } from '../errors/invalid-argument-error'; +import { InvalidClientError } from '../errors/invalid-client-error'; +import { InvalidRequestError } from '../errors/invalid-request-error'; +import { OAuthError } from '../errors/oauth-error'; +import { ServerError } from '../errors/server-error'; +import { UnauthorizedClientError } from '../errors/unauthorized-client-error'; +import { UnsupportedGrantTypeError } from '../errors/unsupported-grant-type-error'; +import { AuthorizationCodeGrantType } from '../grant-types/authorization-code-grant-type'; +import { ClientCredentialsGrantType } from '../grant-types/client-credentials-grant-type'; +import { PasswordGrantType } from '../grant-types/password-grant-type'; +import { RefreshTokenGrantType } from '../grant-types/refresh-token-grant-type'; +import { TokenModel } from '../models/token-model'; +import { Request } from '../request'; +import { Response } from '../response'; +import { BearerTokenType } from '../token-types/bearer-token-type'; +import * as is from '../validator/is'; +/** + * Grant types. + */ + +const grantTypes = { + authorization_code: AuthorizationCodeGrantType, + client_credentials: ClientCredentialsGrantType, + password: PasswordGrantType, + refresh_token: RefreshTokenGrantType, +}; + +export class TokenHandler { + accessTokenLifetime: any; + grantTypes: any; + model: any; + refreshTokenLifetime: any; + allowExtendedTokenAttributes: any; + requireClientAuthentication: any; + alwaysIssueNewRefreshToken: boolean; + constructor(options: any = {}) { + if (!options.accessTokenLifetime) { + throw new InvalidArgumentError( + 'Missing parameter: `accessTokenLifetime`', + ); + } + + if (!options.model) { + throw new InvalidArgumentError('Missing parameter: `model`'); + } + + if (!options.refreshTokenLifetime) { + throw new InvalidArgumentError( + 'Missing parameter: `refreshTokenLifetime`', + ); + } + + if (!options.model.getClient) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `getClient()`', + ); + } + + this.accessTokenLifetime = options.accessTokenLifetime; + this.grantTypes = { ...grantTypes, ...options.extendedGrantTypes }; + this.model = options.model; + this.refreshTokenLifetime = options.refreshTokenLifetime; + this.allowExtendedTokenAttributes = options.allowExtendedTokenAttributes; + this.requireClientAuthentication = + options.requireClientAuthentication || {}; + this.alwaysIssueNewRefreshToken = + options.alwaysIssueNewRefreshToken !== false; + } + + /** + * Token Handler. + */ + + async handle(request: Request, response: Response) { + if (!(request instanceof Request)) { + throw new InvalidArgumentError( + 'Invalid argument: `request` must be an instance of Request', + ); + } + + if (!(response instanceof Response)) { + throw new InvalidArgumentError( + 'Invalid argument: `response` must be an instance of Response', + ); + } + + if (request.method !== 'POST') { + // return Promise.reject( + throw new InvalidRequestError('Invalid request: method must be POST'); + // ); + } + + if (!request.is('application/x-www-form-urlencoded')) { + throw new InvalidRequestError( + 'Invalid request: content must be application/x-www-form-urlencoded', + ); + } + try { + const client = await this.getClient(request, response); + const data = await this.handleGrantType(request, client); + const model = new TokenModel(data, { + allowExtendedTokenAttributes: this.allowExtendedTokenAttributes, + }); + const tokenType = this.getTokenType(model); + this.updateSuccessResponse(response, tokenType); + + return data; + } catch (e) { + if (!(e instanceof OAuthError)) { + e = new ServerError(e); + } + this.updateErrorResponse(response, e); + throw e; + } + } + + /** + * Get the client from the model. + */ + + async getClient(request, response) { + const credentials = this.getClientCredentials(request); + const grantType = request.body.grant_type; + + if (!credentials.clientId) { + throw new InvalidRequestError('Missing parameter: `client_id`'); + } + + if ( + this.isClientAuthenticationRequired(grantType) && + !credentials.clientSecret + ) { + throw new InvalidRequestError('Missing parameter: `client_secret`'); + } + + if (!is.vschar(credentials.clientId)) { + throw new InvalidRequestError('Invalid parameter: `client_id`'); + } + + if (credentials.clientSecret && !is.vschar(credentials.clientSecret)) { + throw new InvalidRequestError('Invalid parameter: `client_secret`'); + } + try { + const client = await this.model.getClient( + credentials.clientId, + credentials.clientSecret, + ); + if (!client) { + throw new InvalidClientError('Invalid client: client is invalid'); + } + + if (!client.grants) { + throw new ServerError('Server error: missing client `grants`'); + } + + if (!(client.grants instanceof Array)) { + throw new ServerError('Server error: `grants` must be an array'); + } + + return client; + } catch (e) { + // Include the "WWW-Authenticate" response header field if the client + // attempted to authenticate via the "Authorization" request header. + // + // @see https://tools.ietf.org/html/rfc6749#section-5.2. + if (e instanceof InvalidClientError && request.get('authorization')) { + response.set('WWW-Authenticate', 'Basic realm="Service"'); + + throw new InvalidClientError(e, { code: 401 }); + } + + throw e; + } + } + + /** + * Get client credentials. + * + * The client credentials may be sent using the HTTP Basic authentication scheme or, alternatively, + * the `client_id` and `client_secret` can be embedded in the body. + * + * @see https://tools.ietf.org/html/rfc6749#section-2.3.1 + */ + + getClientCredentials(request) { + const credentials = auth(request); + const grantType = request.body.grant_type; + + if (credentials) { + return { + clientId: credentials.name, + clientSecret: credentials.pass, + }; + } + + if (request.body.client_id && request.body.client_secret) { + return { + clientId: request.body.client_id, + clientSecret: request.body.client_secret, + }; + } + + if (!this.isClientAuthenticationRequired(grantType)) { + if (request.body.client_id) { + return { clientId: request.body.client_id }; + } + } + + throw new InvalidClientError( + 'Invalid client: cannot retrieve client credentials', + ); + } + + /** + * Handle grant type. + */ + + async handleGrantType(request: Request, client) { + const grantType = request.body.grant_type; + + if (!grantType) { + throw new InvalidRequestError('Missing parameter: `grant_type`'); + } + + if (!is.nchar(grantType) && !is.uri(grantType)) { + throw new InvalidRequestError('Invalid parameter: `grant_type`'); + } + + if (!has(this.grantTypes, grantType)) { + throw new UnsupportedGrantTypeError( + 'Unsupported grant type: `grant_type` is invalid', + ); + } + + if (!client.grants.includes(grantType)) { + throw new UnauthorizedClientError( + 'Unauthorized client: `grant_type` is invalid', + ); + } + + const accessTokenLifetime = this.getAccessTokenLifetime(client); + const refreshTokenLifetime = this.getRefreshTokenLifetime(client); + const Type = this.grantTypes[grantType]; + + const options = { + accessTokenLifetime, + model: this.model, + refreshTokenLifetime, + alwaysIssueNewRefreshToken: this.alwaysIssueNewRefreshToken, + }; + + return new Type(options).handle(request, client); + } + + /** + * Get access token lifetime. + */ + + getAccessTokenLifetime(client) { + return client.accessTokenLifetime || this.accessTokenLifetime; + } + + /** + * Get refresh token lifetime. + */ + + getRefreshTokenLifetime(client) { + return client.refreshTokenLifetime || this.refreshTokenLifetime; + } + + /** + * Get token type. + */ + + getTokenType = model => { + return new BearerTokenType( + model.accessToken, + model.accessTokenLifetime, + model.refreshToken, + model.scope, + model.customAttributes, + ); + }; + + /** + * Update response when a token is generated. + */ + + updateSuccessResponse = (response, tokenType) => { + response.body = tokenType.valueOf(); + + response.set('Cache-Control', 'no-store'); + response.set('Pragma', 'no-cache'); + }; + + /** + * Update response when an error is thrown. + */ + + updateErrorResponse = (response, error) => { + response.body = { + error: error.name, + error_description: error.message, + }; + + response.status = error.code; + }; + + /** + * Given a grant type, check if client authentication is required + */ + isClientAuthenticationRequired = grantType => { + if (Object.keys(this.requireClientAuthentication).length > 0) { + return typeof this.requireClientAuthentication[grantType] !== 'undefined' + ? this.requireClientAuthentication[grantType] + : true; + } + + return true; + }; +} diff --git a/lib/interfaces/authorization-code.interface.ts b/lib/interfaces/authorization-code.interface.ts new file mode 100644 index 000000000..20e72eccc --- /dev/null +++ b/lib/interfaces/authorization-code.interface.ts @@ -0,0 +1,15 @@ +import { Client } from './client.interface'; +import { User } from './user.interface'; + +/** + * An interface representing the authorization code and associated data. + */ +export interface AuthorizationCode { + authorizationCode: string; + expiresAt: Date; + redirectUri: string; + scope?: string; + client: Client; + user: User; + [key: string]: any; +} diff --git a/lib/interfaces/client.interface.ts b/lib/interfaces/client.interface.ts new file mode 100644 index 000000000..dcdb074fe --- /dev/null +++ b/lib/interfaces/client.interface.ts @@ -0,0 +1,11 @@ +/** + * An interface representing the client and associated data + */ +export interface Client { + id: string; + redirectUris?: string | string[]; + grants: string | string[]; + accessTokenLifetime?: number; + refreshTokenLifetime?: number; + [key: string]: any; +} diff --git a/lib/interfaces/model.interface.ts b/lib/interfaces/model.interface.ts new file mode 100644 index 000000000..428a43e82 --- /dev/null +++ b/lib/interfaces/model.interface.ts @@ -0,0 +1,189 @@ +import { AuthorizationCode } from './authorization-code.interface'; +import { Client } from './client.interface'; +import { RefreshToken } from './refresh-token.interface'; +import { Token } from './token.interface'; +import { User } from './user.interface'; + +export interface BaseModel { + /** + * Invoked to generate a new access token. + * + */ + generateAccessToken?( + client: Client, + user: User, + scope: string | string[], + ): Promise; + + /** + * Invoked to retrieve a client using a client id or a + * client id/client secret combination, depending on the grant type. + * + */ + getClient(clientId: string, clientSecret?: string): Promise; + + /** + * Invoked to save an access token and optionally a refresh token, depending on the grant type. + * + */ + saveToken(token: Token, client: Client, user: User): Promise; +} + +export interface RequestAuthenticationModel { + /** + * Invoked to retrieve an existing access token previously saved through Model#saveToken(). + * + */ + getAccessToken(accessToken: string): Promise; + + /** + * Invoked during request authentication to check if + * the provided access token was authorized the requested scopes. + * + */ + verifyScope(token: Token, scope: string | string[]): Promise; +} + +export interface AuthorizationCodeModel + extends BaseModel, + RequestAuthenticationModel { + /** + * Invoked to generate a new refresh token. + * + */ + generateRefreshToken?( + client: Client, + user: User, + scope: string | string[], + ): Promise; + + /** + * Invoked to generate a new authorization code. + * + */ + generateAuthorizationCode?( + client: Client, + user: User, + scope: string | string[], + ): Promise; + + /** + * Invoked to retrieve an existing authorization + * code previously saved through Model#saveAuthorizationCode(). + * + */ + getAuthorizationCode(authorizationCode: string): Promise; + + /** + * Invoked to save an authorization code. + * + */ + saveAuthorizationCode( + code: AuthorizationCode, + client: Client, + user: User, + ): Promise; + + /** + * Invoked to revoke an authorization code. + * + */ + revokeAuthorizationCode(code: AuthorizationCode): Promise; + + /** + * Invoked to check if the requested scope is + * valid for a particular client/user combination. + * + */ + validateScope?( + user: User, + client: Client, + scope: string | string[], + ): Promise; +} + +export interface PasswordModel extends BaseModel, RequestAuthenticationModel { + /** + * Invoked to generate a new refresh token. + * + */ + generateRefreshToken?( + client: Client, + user: User, + scope: string | string[], + ): Promise; + + /** + * Invoked to retrieve a user using a + * username/password combination. + * + */ + getUser(username: string, password: string): Promise; + + /** + * Invoked to check if the requested scope + * is valid for a particular client/user combination. + * + */ + validateScope?( + user: User, + client: Client, + scope: string | string[], + ): Promise; +} + +export interface RefreshTokenModel + extends BaseModel, + RequestAuthenticationModel { + /** + * Invoked to generate a new refresh token. + * + */ + generateRefreshToken?( + client: Client, + user: User, + scope: string | string[], + ): Promise; + + /** + * Invoked to retrieve an existing refresh token previously saved through Model#saveToken(). + * + */ + getRefreshToken(refreshToken: string): Promise; + + /** + * Invoked to revoke a refresh token. + * + */ + revokeToken(token: RefreshToken | Token): Promise; +} + +export interface ClientCredentialsModel + extends BaseModel, + RequestAuthenticationModel { + /** + * Invoked to retrieve the user associated with the specified client. + * + */ + getUserFromClient(client: Client): Promise; + + /** + * Invoked to check if the requested scope is valid for a particular client/user combination. + * + */ + validateScope?( + user: User, + client: Client, + scope: string | string[], + ): Promise; +} + +export interface ExtensionModel extends BaseModel, RequestAuthenticationModel {} + +export interface Model + extends BaseModel, + RequestAuthenticationModel, + AuthorizationCodeModel, + PasswordModel, + RefreshTokenModel, + ClientCredentialsModel {} diff --git a/lib/interfaces/refresh-token.interface.ts b/lib/interfaces/refresh-token.interface.ts new file mode 100644 index 000000000..47d49ccfc --- /dev/null +++ b/lib/interfaces/refresh-token.interface.ts @@ -0,0 +1,14 @@ +import { Client } from './client.interface'; +import { User } from './user.interface'; + +/** + * An interface representing the refresh token and associated data. + */ +export interface RefreshToken { + refreshToken: string; + refreshTokenExpiresAt?: Date; + scope?: string; + client: Client; + user: User; + [key: string]: any; +} diff --git a/lib/interfaces/token.interface.ts b/lib/interfaces/token.interface.ts new file mode 100644 index 000000000..afa3ccd80 --- /dev/null +++ b/lib/interfaces/token.interface.ts @@ -0,0 +1,16 @@ +import { Client } from './client.interface'; +import { User } from './user.interface'; + +/** + * An interface representing the token(s) and associated data. + */ +export interface Token { + accessToken: string; + accessTokenExpiresAt?: Date; + refreshToken?: string; + refreshTokenExpiresAt?: Date; + scope?: string; + client: Client; + user: User; + [key: string]: any; +} diff --git a/lib/interfaces/user.interface.ts b/lib/interfaces/user.interface.ts new file mode 100644 index 000000000..dc5afd5b0 --- /dev/null +++ b/lib/interfaces/user.interface.ts @@ -0,0 +1,7 @@ +/** + * An interface representing the user. + * A user object is completely transparent to oauth2-server and is simply used as input to model functions. + */ +export interface User { + [key: string]: any; +} diff --git a/lib/models/token-model.js b/lib/models/token-model.js deleted file mode 100644 index c6bc3f8d4..000000000 --- a/lib/models/token-model.js +++ /dev/null @@ -1,65 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var InvalidArgumentError = require('../errors/invalid-argument-error'); - -/** - * Constructor. - */ - -var modelAttributes = ['accessToken', 'accessTokenExpiresAt', 'refreshToken', 'refreshTokenExpiresAt', 'scope', 'client', 'user']; - -function TokenModel(data, options) { - data = data || {}; - - if (!data.accessToken) { - throw new InvalidArgumentError('Missing parameter: `accessToken`'); - } - - if (!data.client) { - throw new InvalidArgumentError('Missing parameter: `client`'); - } - - if (!data.user) { - throw new InvalidArgumentError('Missing parameter: `user`'); - } - - if (data.accessTokenExpiresAt && !(data.accessTokenExpiresAt instanceof Date)) { - throw new InvalidArgumentError('Invalid parameter: `accessTokenExpiresAt`'); - } - - if (data.refreshTokenExpiresAt && !(data.refreshTokenExpiresAt instanceof Date)) { - throw new InvalidArgumentError('Invalid parameter: `refreshTokenExpiresAt`'); - } - - this.accessToken = data.accessToken; - this.accessTokenExpiresAt = data.accessTokenExpiresAt; - this.client = data.client; - this.refreshToken = data.refreshToken; - this.refreshTokenExpiresAt = data.refreshTokenExpiresAt; - this.scope = data.scope; - this.user = data.user; - - if (options && options.allowExtendedTokenAttributes) { - this.customAttributes = {}; - - for (var key in data) { - if (data.hasOwnProperty(key) && (modelAttributes.indexOf(key) < 0)) { - this.customAttributes[key] = data[key]; - } - } - } - - if(this.accessTokenExpiresAt) { - this.accessTokenLifetime = Math.floor((this.accessTokenExpiresAt - new Date()) / 1000); - } -} - -/** - * Export constructor. - */ - -module.exports = TokenModel; diff --git a/lib/models/token-model.ts b/lib/models/token-model.ts new file mode 100755 index 000000000..a8ded4015 --- /dev/null +++ b/lib/models/token-model.ts @@ -0,0 +1,81 @@ +import { InvalidArgumentError } from '../errors/invalid-argument-error'; +import { Client } from '../interfaces/client.interface'; +import { Token } from '../interfaces/token.interface'; +import { User } from '../interfaces/user.interface'; + +const modelAttributes = [ + 'accessToken', + 'accessTokenExpiresAt', + 'client', + 'refreshToken', + 'refreshTokenExpiresAt', + 'scope', + 'user', +]; + +export class TokenModel implements Token { + accessToken: string; + accessTokenExpiresAt?: Date; + refreshToken?: string; + refreshTokenExpiresAt?: Date; + scope?: string; + client: Client; + user: User; + customAttributes: {}; + accessTokenLifetime: number; + constructor(data: any = {}, options: any = {}) { + if (!data.accessToken) { + throw new InvalidArgumentError('Missing parameter: `accessToken`'); + } + + if (!data.client) { + throw new InvalidArgumentError('Missing parameter: `client`'); + } + + if (!data.user) { + throw new InvalidArgumentError('Missing parameter: `user`'); + } + + if ( + data.accessTokenExpiresAt && + !(data.accessTokenExpiresAt instanceof Date) + ) { + throw new InvalidArgumentError( + 'Invalid parameter: `accessTokenExpiresAt`', + ); + } + + if ( + data.refreshTokenExpiresAt && + !(data.refreshTokenExpiresAt instanceof Date) + ) { + throw new InvalidArgumentError( + 'Invalid parameter: `refreshTokenExpiresAt`', + ); + } + + this.accessToken = data.accessToken; + this.accessTokenExpiresAt = data.accessTokenExpiresAt; + this.client = data.client; + this.refreshToken = data.refreshToken; + this.refreshTokenExpiresAt = data.refreshTokenExpiresAt; + this.scope = data.scope; + this.user = data.user; + + if (options && options.allowExtendedTokenAttributes) { + this.customAttributes = {}; + + for (const key in data) { + if (data.hasOwnProperty(key) && modelAttributes.indexOf(key) < 0) { + this.customAttributes[key] = data[key]; + } + } + } + const msInS = 1000; + if (this.accessTokenExpiresAt) { + this.accessTokenLifetime = Math.floor( + (this.accessTokenExpiresAt.getTime() - new Date().getTime()) / msInS, + ); + } + } +} diff --git a/lib/request.js b/lib/request.js deleted file mode 100644 index 65e2db303..000000000 --- a/lib/request.js +++ /dev/null @@ -1,73 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var InvalidArgumentError = require('./errors/invalid-argument-error'); -var typeis = require('type-is'); - -/** - * Constructor. - */ - -function Request(options) { - options = options || {}; - - if (!options.headers) { - throw new InvalidArgumentError('Missing parameter: `headers`'); - } - - if (!options.method) { - throw new InvalidArgumentError('Missing parameter: `method`'); - } - - if (!options.query) { - throw new InvalidArgumentError('Missing parameter: `query`'); - } - - this.body = options.body || {}; - this.headers = {}; - this.method = options.method; - this.query = options.query; - - // Store the headers in lower case. - for (var field in options.headers) { - if (options.headers.hasOwnProperty(field)) { - this.headers[field.toLowerCase()] = options.headers[field]; - } - } - - // Store additional properties of the request object passed in - for (var property in options) { - if (options.hasOwnProperty(property) && !this[property]) { - this[property] = options[property]; - } - } -} - -/** - * Get a request header. - */ - -Request.prototype.get = function(field) { - return this.headers[field.toLowerCase()]; -}; - -/** - * Check if the content-type matches any of the given mime type. - */ - -Request.prototype.is = function(types) { - if (!Array.isArray(types)) { - types = [].slice.call(arguments); - } - - return typeis(this, types) || false; -}; - -/** - * Export constructor. - */ - -module.exports = Request; diff --git a/lib/request.ts b/lib/request.ts new file mode 100755 index 000000000..803e415b2 --- /dev/null +++ b/lib/request.ts @@ -0,0 +1,64 @@ +import * as typeis from 'type-is'; +import { InvalidArgumentError } from './errors/invalid-argument-error'; + +export class Request { + body: any; + headers: any; + method: any; + query: any; + constructor(options: any = {}) { + if (!options.headers) { + throw new InvalidArgumentError('Missing parameter: `headers`'); + } + + if (!options.method) { + throw new InvalidArgumentError('Missing parameter: `method`'); + } + + if (!options.query) { + throw new InvalidArgumentError('Missing parameter: `query`'); + } + + this.body = options.body || {}; + this.headers = {}; + this.method = options.method; + this.query = options.query; + + // Store the headers in lower case. + for (const field in options.headers) { + if (options.headers.hasOwnProperty(field)) { + this.headers[field.toLowerCase()] = options.headers[field]; + } + } + + // Store additional properties of the request object passed in + for (const property in options) { + if (options.hasOwnProperty(property) && !this[property]) { + this[property] = options[property]; + } + } + } + + /** + * Get a request header. + */ + + get(field) { + return this.headers[field.toLowerCase()]; + } + + /** + * Check if the content-type matches any of the given mime type. + */ + public is(args: string[]): string | false; + public is(...args: string[]): string | false; + + is(...args) { + let types = args; + if (Array.isArray(types[0])) { + types = types[0]; + } + + return typeis(this as any, types) || false; + } +} diff --git a/lib/response-types/code-response-type.js b/lib/response-types/code-response-type.js deleted file mode 100644 index 6eaf23a89..000000000 --- a/lib/response-types/code-response-type.js +++ /dev/null @@ -1,43 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var InvalidArgumentError = require('../errors/invalid-argument-error'); -var url = require('url'); - -/** - * Constructor. - */ - -function CodeResponseType(code) { - if (!code) { - throw new InvalidArgumentError('Missing parameter: `code`'); - } - - this.code = code; -} - -/** - * Build redirect uri. - */ - -CodeResponseType.prototype.buildRedirectUri = function(redirectUri) { - if (!redirectUri) { - throw new InvalidArgumentError('Missing parameter: `redirectUri`'); - } - - var uri = url.parse(redirectUri, true); - - uri.query.code = this.code; - uri.search = null; - - return uri; -}; - -/** - * Export constructor. - */ - -module.exports = CodeResponseType; diff --git a/lib/response-types/code-response-type.ts b/lib/response-types/code-response-type.ts new file mode 100755 index 000000000..508b7f4a9 --- /dev/null +++ b/lib/response-types/code-response-type.ts @@ -0,0 +1,29 @@ +import { parse } from 'url'; +import { InvalidArgumentError } from '../errors/invalid-argument-error'; + +export class CodeResponseType { + code: any; + constructor(code: number) { + if (!code) { + throw new InvalidArgumentError('Missing parameter: `code`'); + } + this.code = code; + } + + /** + * Build redirect uri. + */ + + buildRedirectUri(redirectUri: string) { + if (!redirectUri) { + throw new InvalidArgumentError('Missing parameter: `redirectUri`'); + } + + const uri = parse(redirectUri, true); + + uri.query.code = this.code; + uri.search = undefined; + + return uri; + } +} diff --git a/lib/response-types/token-response-type.js b/lib/response-types/token-response-type.js deleted file mode 100644 index 2637f64cd..000000000 --- a/lib/response-types/token-response-type.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var ServerError = require('../errors/server-error'); - -/** - * Constructor. - */ - -function TokenResponseType() { - throw new ServerError('Not implemented.'); -} - -/** - * Export constructor. - */ - -module.exports = TokenResponseType; diff --git a/lib/response-types/token-response-type.ts b/lib/response-types/token-response-type.ts new file mode 100755 index 000000000..d35927bed --- /dev/null +++ b/lib/response-types/token-response-type.ts @@ -0,0 +1,8 @@ +import { ServerError } from '../errors/server-error'; + +// tslint:disable-next-line:no-unnecessary-class +export class TokenResponseType { + constructor() { + throw new ServerError('Not implemented.'); + } +} diff --git a/lib/response.js b/lib/response.js deleted file mode 100644 index 4e9d1ec69..000000000 --- a/lib/response.js +++ /dev/null @@ -1,58 +0,0 @@ -'use strict'; - -/** - * Constructor. - */ - -function Response(options) { - options = options || {}; - - this.body = options.body || {}; - this.headers = {}; - this.status = 200; - - // Store the headers in lower case. - for (var field in options.headers) { - if (options.headers.hasOwnProperty(field)) { - this.headers[field.toLowerCase()] = options.headers[field]; - } - } - - // Store additional properties of the response object passed in - for (var property in options) { - if (options.hasOwnProperty(property) && !this[property]) { - this[property] = options[property]; - } - } -} - -/** - * Get a response header. - */ - -Response.prototype.get = function(field) { - return this.headers[field.toLowerCase()]; -}; - -/** - * Redirect response. - */ - -Response.prototype.redirect = function(url) { - this.set('Location', url); - this.status = 302; -}; - -/** - * Set a response header. - */ - -Response.prototype.set = function(field, value) { - this.headers[field.toLowerCase()] = value; -}; - -/** - * Export constructor. - */ - -module.exports = Response; diff --git a/lib/response.ts b/lib/response.ts new file mode 100755 index 000000000..b3cdf8009 --- /dev/null +++ b/lib/response.ts @@ -0,0 +1,49 @@ +export class Response { + body: any; + headers: any; + status: number; + constructor(options: any = {}) { + this.body = options.body || {}; + this.headers = {}; + this.status = 200; + + // Store the headers in lower case. + for (const field in options.headers) { + if (options.headers.hasOwnProperty(field)) { + this.headers[field.toLowerCase()] = options.headers[field]; + } + } + + // Store additional properties of the response object passed in + for (const property in options) { + if (options.hasOwnProperty(property) && !this[property]) { + this[property] = options[property]; + } + } + } + + /** + * Get a response header. + */ + + get(field) { + return this.headers[field.toLowerCase()]; + } + + /** + * Redirect response. + */ + + redirect(url: string) { + this.set('Location', url); + this.status = 302; + } + + /** + * Set a response header. + */ + + set(field: string, value: string) { + this.headers[field.toLowerCase()] = value; + } +} diff --git a/lib/server.js b/lib/server.js deleted file mode 100644 index fba9ccf81..000000000 --- a/lib/server.js +++ /dev/null @@ -1,83 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var _ = require('lodash'); -var AuthenticateHandler = require('./handlers/authenticate-handler'); -var AuthorizeHandler = require('./handlers/authorize-handler'); -var InvalidArgumentError = require('./errors/invalid-argument-error'); -var TokenHandler = require('./handlers/token-handler'); - -/** - * Constructor. - */ - -function OAuth2Server(options) { - options = options || {}; - - if (!options.model) { - throw new InvalidArgumentError('Missing parameter: `model`'); - } - - this.options = options; -} - -/** - * Authenticate a token. - */ - -OAuth2Server.prototype.authenticate = function(request, response, options, callback) { - if (typeof options === 'string') { - options = {scope: options}; - } - - options = _.assign({ - addAcceptedScopesHeader: true, - addAuthorizedScopesHeader: true, - allowBearerTokensInQueryString: false - }, this.options, options); - - return new AuthenticateHandler(options) - .handle(request, response) - .nodeify(callback); -}; - -/** - * Authorize a request. - */ - -OAuth2Server.prototype.authorize = function(request, response, options, callback) { - options = _.assign({ - allowEmptyState: false, - authorizationCodeLifetime: 5 * 60 // 5 minutes. - }, this.options, options); - - return new AuthorizeHandler(options) - .handle(request, response) - .nodeify(callback); -}; - -/** - * Create a token. - */ - -OAuth2Server.prototype.token = function(request, response, options, callback) { - options = _.assign({ - accessTokenLifetime: 60 * 60, // 1 hour. - refreshTokenLifetime: 60 * 60 * 24 * 14, // 2 weeks. - allowExtendedTokenAttributes: false, - requireClientAuthentication: {} // defaults to true for all grant types - }, this.options, options); - - return new TokenHandler(options) - .handle(request, response) - .nodeify(callback); -}; - -/** - * Export constructor. - */ - -module.exports = OAuth2Server; diff --git a/lib/server.ts b/lib/server.ts new file mode 100755 index 000000000..506fb2693 --- /dev/null +++ b/lib/server.ts @@ -0,0 +1,110 @@ +import { AccessDeniedError } from './errors/access-denied-error'; +import { InsufficientScopeError } from './errors/insufficient-scope-error'; +import { InvalidArgumentError } from './errors/invalid-argument-error'; +import { InvalidClientError } from './errors/invalid-client-error'; +import { InvalidGrantError } from './errors/invalid-grant-error'; +import { InvalidRequestError } from './errors/invalid-request-error'; +import { InvalidScopeError } from './errors/invalid-scope-error'; +import { InvalidTokenError } from './errors/invalid-token-error'; +import { OAuthError } from './errors/oauth-error'; +import { ServerError } from './errors/server-error'; +import { UnauthorizedClientError } from './errors/unauthorized-client-error'; +import { UnauthorizedRequestError } from './errors/unauthorized-request-error'; +import { UnsupportedGrantTypeError } from './errors/unsupported-grant-type-error'; +import { UnsupportedResponseTypeError } from './errors/unsupported-response-type-error'; +import { AbstractGrantType } from './grant-types/abstract-grant-type'; +import { AuthenticateHandler } from './handlers/authenticate-handler'; +import { AuthorizeHandler } from './handlers/authorize-handler'; +import { TokenHandler } from './handlers/token-handler'; +import { Request } from './request'; +import { Response } from './response'; + +export class OAuth2Server { + options: any; + constructor(options: any = {}) { + if (!options.model) { + throw new InvalidArgumentError('Missing parameter: `model`'); + } + + this.options = options; + } + + /** + * Authenticate a token. + */ + authenticate(request: Request, response?: Response, scope?: string); + // tslint:disable-next-line:unified-signatures + authenticate(request: Request, response?: Response, options?: any); + + async authenticate( + request: Request, + response?: Response, + options?: string | any, + ) { + let opt = options; + if (typeof opt === 'string') { + opt = { scope: opt }; + } + + opt = { + addAcceptedScopesHeader: true, + addAuthorizedScopesHeader: true, + allowBearerTokensInQueryString: false, + ...this.options, + ...opt, + }; + + return new AuthenticateHandler(opt).handle(request, response); + } + + /** + * Authorize a request. + */ + + async authorize(request: Request, response: Response, options?: any) { + const defaultLifeTime = 300; + const opts = { + allowEmptyState: false, + authorizationCodeLifetime: defaultLifeTime, + ...this.options, + ...options, + }; + + return new AuthorizeHandler(opts).handle(request, response); + } + + /** + * Create a token. + */ + + async token(request: Request, response: Response, options?: any) { + const opts = { + accessTokenLifetime: 60 * 60, // 1 hour. + refreshTokenLifetime: 60 * 60 * 24 * 14, // 2 weeks. + allowExtendedTokenAttributes: false, + requireClientAuthentication: {}, + ...this.options, + ...options, + }; + + return new TokenHandler(opts).handle(request, response); + } + + static Request = Request; + static Response = Response; + static AbstractGrantType = AbstractGrantType; + static AccessDeniedError = AccessDeniedError; + static InsufficientScopeError = InsufficientScopeError; + static InvalidArgumentError = InvalidArgumentError; + static InvalidClientError = InvalidClientError; + static InvalidGrantError = InvalidGrantError; + static InvalidRequestError = InvalidRequestError; + static InvalidScopeError = InvalidScopeError; + static InvalidTokenError = InvalidTokenError; + static OAuthError = OAuthError; + static ServerError = ServerError; + static UnauthorizedClientError = UnauthorizedClientError; + static UnauthorizedRequestError = UnauthorizedRequestError; + static UnsupportedGrantTypeError = UnsupportedGrantTypeError; + static UnsupportedResponseTypeError = UnsupportedResponseTypeError; +} diff --git a/lib/token-types/bearer-token-type.js b/lib/token-types/bearer-token-type.js deleted file mode 100644 index 9124cb2e8..000000000 --- a/lib/token-types/bearer-token-type.js +++ /dev/null @@ -1,62 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var InvalidArgumentError = require('../errors/invalid-argument-error'); - -/** - * Constructor. - */ - -function BearerTokenType(accessToken, accessTokenLifetime, refreshToken, scope, customAttributes) { - if (!accessToken) { - throw new InvalidArgumentError('Missing parameter: `accessToken`'); - } - - this.accessToken = accessToken; - this.accessTokenLifetime = accessTokenLifetime; - this.refreshToken = refreshToken; - this.scope = scope; - - if (customAttributes) { - this.customAttributes = customAttributes; - } -} - -/** - * Retrieve the value representation. - */ - -BearerTokenType.prototype.valueOf = function() { - var object = { - access_token: this.accessToken, - token_type: 'Bearer' - }; - - if (this.accessTokenLifetime) { - object.expires_in = this.accessTokenLifetime; - } - - if (this.refreshToken) { - object.refresh_token = this.refreshToken; - } - - if (this.scope) { - object.scope = this.scope; - } - - for (var key in this.customAttributes) { - if (this.customAttributes.hasOwnProperty(key)) { - object[key] = this.customAttributes[key]; - } - } - return object; -}; - -/** - * Export constructor. - */ - -module.exports = BearerTokenType; diff --git a/lib/token-types/bearer-token-type.ts b/lib/token-types/bearer-token-type.ts new file mode 100755 index 000000000..d58821a77 --- /dev/null +++ b/lib/token-types/bearer-token-type.ts @@ -0,0 +1,60 @@ +import { InvalidArgumentError } from '../errors/invalid-argument-error'; + +export class BearerTokenType { + accessToken: string; + accessTokenLifetime: number; + refreshToken: string; + scope: string; + customAttributes: any; + constructor( + accessToken: string, + accessTokenLifetime: number, + refreshToken: string, + scope: string, + customAttributes: any, + ) { + if (!accessToken) { + throw new InvalidArgumentError('Missing parameter: `accessToken`'); + } + + this.accessToken = accessToken; + this.accessTokenLifetime = accessTokenLifetime; + this.refreshToken = refreshToken; + this.scope = scope; + + if (customAttributes) { + this.customAttributes = customAttributes; + } + } + + /** + * Retrieve the value representation. + */ + + valueOf() { + const object: any = { + access_token: this.accessToken, + token_type: 'Bearer', + }; + + if (this.accessTokenLifetime) { + object.expires_in = this.accessTokenLifetime; + } + + if (this.refreshToken) { + object.refresh_token = this.refreshToken; + } + + if (this.scope) { + object.scope = this.scope; + } + + for (const key in this.customAttributes) { + if (this.customAttributes.hasOwnProperty(key)) { + object[key] = this.customAttributes[key]; + } + } + + return object; + } +} diff --git a/lib/token-types/mac-token-type.js b/lib/token-types/mac-token-type.js deleted file mode 100644 index 9fdc600c6..000000000 --- a/lib/token-types/mac-token-type.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var ServerError = require('../errors/server-error'); - -/** - * Constructor. - */ - -function MacTokenType() { - throw new ServerError('Not implemented.'); -} - -/** - * Export constructor. - */ - -module.exports = MacTokenType; diff --git a/lib/token-types/mac-token-type.ts b/lib/token-types/mac-token-type.ts new file mode 100755 index 000000000..730211f95 --- /dev/null +++ b/lib/token-types/mac-token-type.ts @@ -0,0 +1,8 @@ +import { ServerError } from '../errors/server-error'; + +// tslint:disable-next-line:no-unnecessary-class +export class MacTokenType { + constructor() { + throw new ServerError('Not implemented.'); + } +} diff --git a/lib/types/falsy.type.ts b/lib/types/falsy.type.ts new file mode 100644 index 000000000..eb543423d --- /dev/null +++ b/lib/types/falsy.type.ts @@ -0,0 +1 @@ +type Falsy = '' | 0 | false | null | undefined; diff --git a/lib/utils/token-util.js b/lib/utils/token-util.js deleted file mode 100644 index 0f73746bb..000000000 --- a/lib/utils/token-util.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var crypto = require('crypto'); -var randomBytes = require('bluebird').promisify(require('crypto').randomBytes); - -/** - * Export `TokenUtil`. - */ - -module.exports = { - - /** - * Generate random token. - */ - - generateRandomToken: function() { - return randomBytes(256).then(function(buffer) { - return crypto - .createHash('sha1') - .update(buffer) - .digest('hex'); - }); - } - -}; diff --git a/lib/utils/token-util.ts b/lib/utils/token-util.ts new file mode 100755 index 000000000..9e9270ed0 --- /dev/null +++ b/lib/utils/token-util.ts @@ -0,0 +1,16 @@ +import { createHash, randomBytes } from 'crypto'; +import { promisify } from 'util'; +const randomBytesPromise = promisify(randomBytes); + +/** + * Generate random token. + */ + +export const GenerateRandomToken = async () => { + const bytesSize = 256; + const buffer = await randomBytesPromise(bytesSize); + + return createHash('sha1') + .update(buffer) + .digest('hex'); +}; diff --git a/lib/validator/is.js b/lib/validator/is.js deleted file mode 100644 index 07af6cb64..000000000 --- a/lib/validator/is.js +++ /dev/null @@ -1,81 +0,0 @@ -'use strict'; - -/** - * Validation rules. - */ - -var rules = { - NCHAR: /^[\u002D|\u002E|\u005F|\w]+$/, - NQCHAR: /^[\u0021|\u0023-\u005B|\u005D-\u007E]+$/, - NQSCHAR: /^[\u0020-\u0021|\u0023-\u005B|\u005D-\u007E]+$/, - UNICODECHARNOCRLF: /^[\u0009|\u0020-\u007E|\u0080-\uD7FF|\uE000-\uFFFD|\u10000-\u10FFFF]+$/, - URI: /^[a-zA-Z][a-zA-Z0-9+.-]+:/, - VSCHAR: /^[\u0020-\u007E]+$/ -}; - -/** - * Export validation functions. - */ - -module.exports = { - - /** - * Validate if a value matches a unicode character. - * - * @see https://tools.ietf.org/html/rfc6749#appendix-A - */ - - nchar: function(value) { - return rules.NCHAR.test(value); - }, - - /** - * Validate if a value matches a unicode character, including exclamation marks. - * - * @see https://tools.ietf.org/html/rfc6749#appendix-A - */ - - nqchar: function(value) { - return rules.NQCHAR.test(value); - }, - - /** - * Validate if a value matches a unicode character, including exclamation marks and spaces. - * - * @see https://tools.ietf.org/html/rfc6749#appendix-A - */ - - nqschar: function(value) { - return rules.NQSCHAR.test(value); - }, - - /** - * Validate if a value matches a unicode character excluding the carriage - * return and linefeed characters. - * - * @see https://tools.ietf.org/html/rfc6749#appendix-A - */ - - uchar: function(value) { - return rules.UNICODECHARNOCRLF.test(value); - }, - - /** - * Validate if a value matches generic URIs. - * - * @see http://tools.ietf.org/html/rfc3986#section-3 - */ - uri: function(value) { - return rules.URI.test(value); - }, - - /** - * Validate if a value matches against the printable set of unicode characters. - * - * @see https://tools.ietf.org/html/rfc6749#appendix-A - */ - - vschar: function(value) { - return rules.VSCHAR.test(value); - } -}; diff --git a/lib/validator/is.ts b/lib/validator/is.ts new file mode 100755 index 000000000..d9475878a --- /dev/null +++ b/lib/validator/is.ts @@ -0,0 +1,64 @@ +/** + * Validation rules. + */ + +const Rules = { + NCHAR: /^[\u002D|\u002E|\u005F|\w]+$/, + NQCHAR: /^[\u0021|\u0023-\u005B|\u005D-\u007E]+$/, + NQSCHAR: /^[\u0020-\u0021|\u0023-\u005B|\u005D-\u007E]+$/, + UNICODECHARNOCRLF: /^[\u0009|\u0020-\u007E|\u0080-\uD7FF|\uE000-\uFFFD|\u10000-\u10FFFF]+$/, + URI: /^[a-zA-Z][a-zA-Z0-9+.-]+:/, + VSCHAR: /^[\u0020-\u007E]+$/, +}; + +/** + * Export validation functions. + */ + +/** + * Validate if a value matches a unicode character. + * + * @see https://tools.ietf.org/html/rfc6749#appendix-A + */ + +export const nchar = (value: string) => Rules.NCHAR.test(value); + +/** + * Validate if a value matches a unicode character, including exclamation marks. + * + * @see https://tools.ietf.org/html/rfc6749#appendix-A + */ + +export const nqchar = (value: string) => Rules.NQCHAR.test(value); + +/** + * Validate if a value matches a unicode character, including exclamation marks and spaces. + * + * @see https://tools.ietf.org/html/rfc6749#appendix-A + */ + +export const nqschar = (value: string) => Rules.NQSCHAR.test(value); + +/** + * Validate if a value matches a unicode character excluding the carriage + * and linefeed characters. + * + * @see https://tools.ietf.org/html/rfc6749#appendix-A + */ + +export const uchar = (value: string) => Rules.UNICODECHARNOCRLF.test(value); + +/** + * Validate if a value matches generic URIs. + * + * @see http://tools.ietf.org/html/rfc3986#section-3 + */ +export const uri = (value: string) => Rules.URI.test(value); + +/** + * Validate if a value matches against the printable set of unicode characters. + * + * @see https://tools.ietf.org/html/rfc6749#appendix-A + */ + +export const vschar = (value: string) => Rules.VSCHAR.test(value); diff --git a/package-lock.json b/package-lock.json new file mode 100755 index 000000000..e6d18d30f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1458 @@ +{ + "name": "oauth2-server", + "version": "3.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@sinonjs/commons": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.4.0.tgz", + "integrity": "sha512-9jHK3YF/8HtJ9wCAbG+j8cD0i0+ATS9A7gXFqS36TblLPNy6rEEc+SB0imo91eCboGaBYGV/MT1/br/J+EE7Tw==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/formatio": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.1.tgz", + "integrity": "sha512-tsHvOB24rvyvV2+zKMmPkZ7dXX6LSLKZ7aOtXY6Edklp0uRcgGpOsQTTGTcWViFyx4uhWc6GV8QdnALbIbIdeQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^3.1.0" + } + }, + "@sinonjs/samsam": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.1.tgz", + "integrity": "sha512-wRSfmyd81swH0hA1bxJZJ57xr22kC07a1N4zuIL47yTS04bDk6AoCkczcqHEjcRPmJ+FruGJ9WBQiJwMtIElFw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.0.2", + "array-from": "^2.1.1", + "lodash": "^4.17.11" + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, + "@types/basic-auth": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@types/basic-auth/-/basic-auth-1.1.2.tgz", + "integrity": "sha512-NzkkcC+gkkILWaBi3+/z/3do6Ybk6TWeTqV5zCVXmG2KaBoT5YqlJvfqP44HCyDA+Cu58pp7uKAxy/G58se/TA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/lodash": { + "version": "4.14.123", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.123.tgz", + "integrity": "sha512-pQvPkc4Nltyx7G1Ww45OjVqUsJP4UsZm+GWJpigXgkikZqJgRm4c48g027o6tdgubWHwFRF15iFd+Y4Pmqv6+Q==", + "dev": true + }, + "@types/mocha": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.6.tgz", + "integrity": "sha512-1axi39YdtBI7z957vdqXI4Ac25e7YihYQtJa+Clnxg1zTJEaIRbndt71O3sP4GAMgiAm0pY26/b9BrY4MR/PMw==", + "dev": true + }, + "@types/node": { + "version": "11.13.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.4.tgz", + "integrity": "sha512-+rabAZZ3Yn7tF/XPGHupKIL5EcAbrLxnTr/hgQICxbeuAfWtT0UZSfULE+ndusckBItcv4o6ZeOJplQikVcLvQ==", + "dev": true + }, + "@types/sinon": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-7.0.11.tgz", + "integrity": "sha512-6ee09Ugx6GyEr0opUIakmxIWFNmqYPjkqa3/BuxCBokA0klsOLPgMD5K4q40lH7/yZVuJVzOfQpd7pipwjngkQ==", + "dev": true + }, + "@types/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-4zJN5gJH+Km6hA36z8MnOKas6EU0qwxItTXNijYDPuZUsSk4EpIAB56fwnxZIhi3tHx42J7wqNdQTqt49Ar9FQ==", + "dev": true + }, + "@types/type-is": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@types/type-is/-/type-is-1.6.2.tgz", + "integrity": "sha512-q8d51ZdF/D8xebrtNDsZH+4XBUFdz8xEgWhE4U4F4WWmcBZ8+i/r/qs9DmjAprYh5qQTYlY4BxaVKDrWIwNQ9w==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "arg": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.0.tgz", + "integrity": "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "es-abstract": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", + "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-keys": "^1.0.12" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + } + }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "is-buffer": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", + "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==", + "dev": true + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "just-extend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz", + "integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==", + "dev": true + }, + "klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "requires": { + "graceful-fs": "^4.1.11" + } + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + }, + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "requires": { + "chalk": "^2.0.1" + } + }, + "lolex": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-3.1.0.tgz", + "integrity": "sha512-zFo5MgCJ0rZ7gQg69S4pqBsLURbFw11X68C18OcJjJQbqaXm2NoTrGl1IMM3TIz0/BnN1tIs2tzmmqvCsOMMjw==", + "dev": true + }, + "make-error": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", + "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", + "dev": true + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + } + }, + "mime-db": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", + "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==" + }, + "mime-types": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", + "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", + "requires": { + "mime-db": "~1.38.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.1.3.tgz", + "integrity": "sha512-QdE/w//EPHrqgT5PNRUjRVHy6IJAzAf1R8n2O8W8K2RZ+NbPfOD5cBDp+PGa2Gptep37C/TdBiaNwakppEzEbg==", + "dev": true, + "requires": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.0", + "log-symbols": "2.2.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "ms": "2.1.1", + "node-environment-flags": "1.0.5", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.2.2", + "yargs-parser": "13.0.0", + "yargs-unparser": "1.5.0" + }, + "dependencies": { + "js-yaml": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.0.tgz", + "integrity": "sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "nise": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.4.10.tgz", + "integrity": "sha512-sa0RRbj53dovjc7wombHmVli9ZihXbXCQ2uH3TNm03DyvOSIQbxg+pbqDKrk2oxMK1rtLGVlKxcB9rrc6X5YjA==", + "dev": true, + "requires": { + "@sinonjs/formatio": "^3.1.0", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "lolex": "^2.3.2", + "path-to-regexp": "^1.7.0" + }, + "dependencies": { + "lolex": { + "version": "2.7.5", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.5.tgz", + "integrity": "sha512-l9x0+1offnKKIzYVjyXU2SiwhXDLekRzKyhnbyldPHvC7BvLPVpdNUNR2KeMAiCN2D/kLNttZgQD5WjSxuBx3Q==", + "dev": true + } + } + }, + "node-environment-flags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", + "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", + "dev": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "resolve": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", + "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "should": { + "version": "13.2.3", + "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", + "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", + "dev": true, + "requires": { + "should-equal": "^2.0.0", + "should-format": "^3.0.3", + "should-type": "^1.4.0", + "should-type-adaptors": "^1.0.1", + "should-util": "^1.0.0" + } + }, + "should-equal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", + "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", + "dev": true, + "requires": { + "should-type": "^1.4.0" + } + }, + "should-format": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", + "integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=", + "dev": true, + "requires": { + "should-type": "^1.3.0", + "should-type-adaptors": "^1.0.1" + } + }, + "should-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", + "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=", + "dev": true + }, + "should-type-adaptors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", + "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", + "dev": true, + "requires": { + "should-type": "^1.3.0", + "should-util": "^1.0.0" + } + }, + "should-util": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.0.tgz", + "integrity": "sha1-yYzaN0qmsZDfi6h8mInCtNtiAGM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "sinon": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.3.1.tgz", + "integrity": "sha512-eQKMaeWovtOtYe2xThEvaHmmxf870Di+bim10c3ZPrL5bZhLGtu8cz+rOBTFz0CwBV4Q/7dYwZiqZbGVLZ+vjQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.4.0", + "@sinonjs/formatio": "^3.2.1", + "@sinonjs/samsam": "^3.3.1", + "diff": "^3.5.0", + "lolex": "^3.1.0", + "nise": "^1.4.10", + "supports-color": "^5.5.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", + "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "ts-node": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.1.0.tgz", + "integrity": "sha512-34jpuOrxDuf+O6iW1JpgTRDFynUZ1iEqtYruBqh35gICNjN8x+LpVcPAcwzLPi9VU6mdA3ym+x233nZmZp445A==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "diff": "^3.1.0", + "make-error": "^1.1.1", + "source-map-support": "^0.5.6", + "yn": "^3.0.0" + } + }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" + }, + "tslint": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.16.0.tgz", + "integrity": "sha512-UxG2yNxJ5pgGwmMzPMYh/CCnCnh0HfPgtlVRDs1ykZklufFBL1ZoTlWFRz2NQjcoEiDoRp+JyT0lhBbbH/obyA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^3.2.0", + "glob": "^7.1.1", + "js-yaml": "^3.13.0", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.29.0" + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-is": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.18" + } + }, + "typescript": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.3.tgz", + "integrity": "sha512-FFgHdPt4T/duxx6Ndf7hwgMZZjZpB+U0nMNGVCYPq0rEzWKjEDobm4J6yb3CS7naZ0yURFqdw9Gwc7UOh/P9oQ==", + "dev": true + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yargs": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz", + "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "os-locale": "^3.1.0", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.0.0.tgz", + "integrity": "sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.5.0.tgz", + "integrity": "sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==", + "dev": true, + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.11", + "yargs": "^12.0.5" + }, + "dependencies": { + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "yn": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.0.tgz", + "integrity": "sha512-kKfnnYkbTfrAdd0xICNFw7Atm8nKpLcLv9AZGEt+kczL/WQVai4e2V6ZN8U/O+iI6WrNuJjNNOyu4zfhl9D3Hg==", + "dev": true + } + } +} diff --git a/package.json b/package.json old mode 100644 new mode 100755 index 6992ef15e..92760f596 --- a/package.json +++ b/package.json @@ -1,43 +1,76 @@ { "name": "oauth2-server", "description": "Complete, framework-agnostic, compliant and well tested module for implementing an OAuth2 Server in node.js", - "version": "3.0.0", + "version": "3.0.1", "keywords": [ "oauth", "oauth2" ], "contributors": [ - { "name": "Thom Seddon", "email": "thom@seddonmedia.co.uk" }, - { "name": "Lars F. Karlström" , "email": "lars@lfk.io" }, - { "name": "Rui Marinho", "email": "ruipmarinho@gmail.com" }, - { "name" : "Tiago Ribeiro", "email": "tiago.ribeiro@gmail.com" }, - { "name": "Michael Salinger", "email": "mjsalinger@gmail.com" }, - { "name": "Nuno Sousa" }, - { "name": "Max Truxa" } + { + "name": "Thom Seddon", + "email": "thom@seddonmedia.co.uk" + }, + { + "name": "Lars F. Karlström", + "email": "lars@lfk.io" + }, + { + "name": "Rui Marinho", + "email": "ruipmarinho@gmail.com" + }, + { + "name": "Tiago Ribeiro", + "email": "tiago.ribeiro@gmail.com" + }, + { + "name": "Michael Salinger", + "email": "mjsalinger@gmail.com" + }, + { + "name": "Nuno Sousa" + }, + { + "name": "Max Truxa" + } ], "main": "index.js", "dependencies": { - "basic-auth": "1.1.0", - "bluebird": "3.5.0", - "lodash": "4.17.4", - "promisify-any": "2.0.1", - "statuses": "1.3.1", - "type-is": "1.6.15" + "basic-auth": "^2.0.1", + "fs-extra": "^7.0.1", + "klaw-sync": "^6.0.0", + "lodash": "^4.17.11", + "statuses": "^1.5.0", + "tslib": "^1.9.3", + "type-is": "^1.6.16" }, "devDependencies": { - "jshint": "2.9.4", - "mocha": "3.3.0", - "should": "11.2.1", - "sinon": "2.3.2" + "mocha": "^6.1.3", + "should": "^13.2.3", + "sinon": "^7.3.1", + "@types/basic-auth": "^1.1.2", + "@types/lodash": "^4.14.123", + "@types/mocha": "^5.2.6", + "@types/node": "^11.13.4", + "@types/sinon": "^7.0.11", + "@types/statuses": "^1.5.0", + "@types/type-is": "^1.6.2", + "ts-node": "^8.1.0", + "tslint": "^5.16.0", + "typescript": "^3.4.3" }, "license": "MIT", "engines": { - "node": ">=4.0" + "node": ">=8.0" }, "scripts": { - "pretest": "./node_modules/.bin/jshint --config ./.jshintrc lib test", - "test": "NODE_ENV=test ./node_modules/.bin/mocha 'test/**/*_test.js'", - "test-debug": "NODE_ENV=test ./node_modules/.bin/mocha --inspect --debug-brk 'test/**/*_test.js'" + "lint": "tslint -p tsconfig.build.json -c tslint.json", + "lint:all": "tslint -p tsconfig.json -c tslint.json", + "pretest": "npx jshint --config ./.jshintrc lib test", + "build": "npx tsc -p tsconfig.build.json", + "test": "NODE_ENV=test npx mocha 'test/**/*_test.js'", + "test2": "npx mocha 'test/**/*.spec.ts' --config 'test/.mocharc.jsonc'", + "test-debug": "NODE_ENV=test npx mocha --inspect --debug-brk 'test/**/*_test.js'" }, "repository": { "type": "git", diff --git a/test/.mocharc.jsonc b/test/.mocharc.jsonc new file mode 100644 index 000000000..a8ff95b9d --- /dev/null +++ b/test/.mocharc.jsonc @@ -0,0 +1,7 @@ +{ + "require": ["ts-node/register", "should", "test/assertions"], + "reporter": "spec", + "slow": 75, + "timeout": 2000, + "ui": "bdd" +} diff --git a/test/assertions.js b/test/assertions.js old mode 100644 new mode 100755 index 6846e3cdf..d372f6674 --- a/test/assertions.js +++ b/test/assertions.js @@ -1,17 +1,14 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var should = require('should'); +const from = require('should'); /** * SHA-1 assertion. */ -should.Assertion.add('sha1', function() { - this.params = { operator: 'to be a valid SHA-1 hash' }; - - this.obj.should.match(/^[a-f0-9]{40}$/i); -}, true); +should.Assertion.add( + 'sha1', + function() { + this.params = { operator: 'to be a valid SHA-1 hash' }; + this.obj.should.match(/^[a-f0-9]{40}$/i); + }, + true, +); diff --git a/test/integration/grant-types/abstract-grant-type.spec.ts b/test/integration/grant-types/abstract-grant-type.spec.ts new file mode 100755 index 000000000..c32478450 --- /dev/null +++ b/test/integration/grant-types/abstract-grant-type.spec.ts @@ -0,0 +1,231 @@ +import * as should from 'should'; +import { InvalidArgumentError } from '../../../lib/errors/invalid-argument-error'; +import { AbstractGrantType } from '../../../lib/grant-types/abstract-grant-type'; +import { Request } from '../../../lib/request'; +/** + * Test `AbstractGrantType` integration. + */ +describe('AbstractGrantType integration', () => { + describe('constructor()', () => { + it('should throw an error if `options.accessTokenLifetime` is missing', () => { + try { + new AbstractGrantType(); + should.fail('no error was thrown', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `accessTokenLifetime`'); + } + }); + + it('should throw an error if `options.model` is missing', () => { + try { + new AbstractGrantType({ accessTokenLifetime: 123 }); + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `model`'); + } + }); + + it('should set the `accessTokenLifetime`', () => { + const grantType = new AbstractGrantType({ + accessTokenLifetime: 123, + model: {}, + }); + + grantType.accessTokenLifetime.should.equal(123); + }); + + it('should set the `model`', () => { + const model = {}; + const grantType = new AbstractGrantType({ + accessTokenLifetime: 123, + model, + }); + + grantType.model.should.equal(model); + }); + + it('should set the `refreshTokenLifetime`', () => { + const grantType = new AbstractGrantType({ + accessTokenLifetime: 123, + model: {}, + refreshTokenLifetime: 456, + }); + + grantType.refreshTokenLifetime.should.equal(456); + }); + }); + + describe('generateAccessToken()', () => { + it('should return an access token', async () => { + const handler = new AbstractGrantType({ + accessTokenLifetime: 123, + model: {}, + refreshTokenLifetime: 456, + }); + try { + const data: any = await handler.generateAccessToken(); + data.should.be.a.sha1(); + } catch (error) { + should.fail('should.fail', ''); + } + }); + + it('should support promises', () => { + const model = { + generateAccessToken() { + return Promise.resolve({}); + }, + }; + const handler = new AbstractGrantType({ + accessTokenLifetime: 123, + model, + refreshTokenLifetime: 456, + }); + + handler.generateAccessToken().should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const model = { + generateAccessToken() { + return {}; + }, + }; + const handler = new AbstractGrantType({ + accessTokenLifetime: 123, + model, + refreshTokenLifetime: 456, + }); + + handler.generateAccessToken().should.be.an.instanceOf(Promise); + }); + }); + + describe('generateRefreshToken()', () => { + it('should return a refresh token', async () => { + const handler = new AbstractGrantType({ + accessTokenLifetime: 123, + model: {}, + refreshTokenLifetime: 456, + }); + try { + const data: any = await handler.generateRefreshToken(); + data.should.be.a.sha1(); + } catch (error) { + should.fail('should.fail fail', error.message); + } + }); + + it('should support promises', () => { + const model = { + generateRefreshToken() { + return Promise.resolve({}); + }, + }; + const handler = new AbstractGrantType({ + accessTokenLifetime: 123, + model, + refreshTokenLifetime: 456, + }); + + handler.generateRefreshToken().should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const model = { + generateRefreshToken() { + return {}; + }, + }; + const handler = new AbstractGrantType({ + accessTokenLifetime: 123, + model, + refreshTokenLifetime: 456, + }); + + handler.generateRefreshToken().should.be.an.instanceOf(Promise); + }); + }); + + describe('getAccessTokenExpiresAt()', () => { + it('should return a date', () => { + const handler = new AbstractGrantType({ + accessTokenLifetime: 123, + model: {}, + refreshTokenLifetime: 456, + }); + + handler.getAccessTokenExpiresAt().should.be.an.instanceOf(Date); + }); + }); + + describe('getRefreshTokenExpiresAt()', () => { + it('should return a refresh token', () => { + const handler = new AbstractGrantType({ + accessTokenLifetime: 123, + model: {}, + refreshTokenLifetime: 456, + }); + + handler.getRefreshTokenExpiresAt().should.be.an.instanceOf(Date); + }); + }); + + describe('getScope()', () => { + it('should throw an error if `scope` is invalid', () => { + const handler = new AbstractGrantType({ + accessTokenLifetime: 123, + model: {}, + refreshTokenLifetime: 456, + }); + const request = new Request({ + body: { scope: 'øå€£‰' }, + headers: {}, + method: {}, + query: {}, + }); + + try { + handler.getScope(request); + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Invalid parameter: `scope`'); + } + }); + + it('should allow the `scope` to be `undefined`', () => { + const handler = new AbstractGrantType({ + accessTokenLifetime: 123, + model: {}, + refreshTokenLifetime: 456, + }); + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: {}, + }); + + should.not.exist(handler.getScope(request)); + }); + + it('should return the scope', () => { + const handler = new AbstractGrantType({ + accessTokenLifetime: 123, + model: {}, + refreshTokenLifetime: 456, + }); + const request = new Request({ + body: { scope: 'foo' }, + headers: {}, + method: {}, + query: {}, + }); + + handler.getScope(request).should.equal('foo'); + }); + }); +}); diff --git a/test/integration/grant-types/abstract-grant-type_test.js b/test/integration/grant-types/abstract-grant-type_test.js deleted file mode 100644 index 6da489cd9..000000000 --- a/test/integration/grant-types/abstract-grant-type_test.js +++ /dev/null @@ -1,174 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var AbstractGrantType = require('../../../lib/grant-types/abstract-grant-type'); -var InvalidArgumentError = require('../../../lib/errors/invalid-argument-error'); -var Promise = require('bluebird'); -var Request = require('../../../lib/request'); -var should = require('should'); - -/** - * Test `AbstractGrantType` integration. - */ - -describe('AbstractGrantType integration', function() { - describe('constructor()', function() { - it('should throw an error if `options.accessTokenLifetime` is missing', function() { - try { - new AbstractGrantType(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `accessTokenLifetime`'); - } - }); - - it('should throw an error if `options.model` is missing', function() { - try { - new AbstractGrantType({ accessTokenLifetime: 123 }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `model`'); - } - }); - - it('should set the `accessTokenLifetime`', function() { - var grantType = new AbstractGrantType({ accessTokenLifetime: 123, model: {} }); - - grantType.accessTokenLifetime.should.equal(123); - }); - - it('should set the `model`', function() { - var model = {}; - var grantType = new AbstractGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.model.should.equal(model); - }); - - it('should set the `refreshTokenLifetime`', function() { - var grantType = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); - - grantType.refreshTokenLifetime.should.equal(456); - }); - }); - - describe('generateAccessToken()', function() { - it('should return an access token', function() { - var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); - - return handler.generateAccessToken() - .then(function(data) { - data.should.be.a.sha1; - }) - .catch(should.fail); - }); - - it('should support promises', function() { - var model = { - generateAccessToken: function() { - return Promise.resolve({}); - } - }; - var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: model, refreshTokenLifetime: 456 }); - - handler.generateAccessToken().should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var model = { - generateAccessToken: function() { - return {}; - } - }; - var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: model, refreshTokenLifetime: 456 }); - - handler.generateAccessToken().should.be.an.instanceOf(Promise); - }); - }); - - describe('generateRefreshToken()', function() { - it('should return a refresh token', function() { - var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); - - return handler.generateRefreshToken() - .then(function(data) { - data.should.be.a.sha1; - }) - .catch(should.fail); - }); - - it('should support promises', function() { - var model = { - generateRefreshToken: function() { - return Promise.resolve({}); - } - }; - var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: model, refreshTokenLifetime: 456 }); - - handler.generateRefreshToken().should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var model = { - generateRefreshToken: function() { - return {}; - } - }; - var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: model, refreshTokenLifetime: 456 }); - - handler.generateRefreshToken().should.be.an.instanceOf(Promise); - }); - }); - - describe('getAccessTokenExpiresAt()', function() { - it('should return a date', function() { - var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); - - handler.getAccessTokenExpiresAt().should.be.an.instanceOf(Date); - }); - }); - - describe('getRefreshTokenExpiresAt()', function() { - it('should return a refresh token', function() { - var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); - - handler.getRefreshTokenExpiresAt().should.be.an.instanceOf(Date); - }); - }); - - describe('getScope()', function() { - it('should throw an error if `scope` is invalid', function() { - var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); - var request = new Request({ body: { scope: 'øå€£‰' }, headers: {}, method: {}, query: {} }); - - try { - handler.getScope(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid parameter: `scope`'); - } - }); - - it('should allow the `scope` to be `undefined`', function() { - var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - should.not.exist(handler.getScope(request)); - }); - - it('should return the scope', function() { - var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); - var request = new Request({ body: { scope: 'foo' }, headers: {}, method: {}, query: {} }); - - handler.getScope(request).should.equal('foo'); - }); - }); -}); diff --git a/test/integration/grant-types/authorization-code-grant-type.spec.ts b/test/integration/grant-types/authorization-code-grant-type.spec.ts new file mode 100755 index 000000000..c089e0039 --- /dev/null +++ b/test/integration/grant-types/authorization-code-grant-type.spec.ts @@ -0,0 +1,1015 @@ +import * as should from 'should'; +import { InvalidArgumentError } from '../../../lib/errors/invalid-argument-error'; +import { InvalidGrantError } from '../../../lib/errors/invalid-grant-error'; +import { InvalidRequestError } from '../../../lib/errors/invalid-request-error'; +import { ServerError } from '../../../lib/errors/server-error'; +import { AuthorizationCodeGrantType } from '../../../lib/grant-types/authorization-code-grant-type'; +import { Request } from '../../../lib/request'; + +/** + * Test `AuthorizationCodeGrantType` integration. + */ + +describe('AuthorizationCodeGrantType integration', () => { + describe('constructor()', () => { + it('should throw an error if `model` is missing', () => { + try { + new AuthorizationCodeGrantType({ accessTokenLifetime: 3600 }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `model`'); + } + }); + + it('should throw an error if the model does not implement `getAuthorizationCode()`', () => { + try { + new AuthorizationCodeGrantType({ + accessTokenLifetime: 3600, + model: {}, + }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `getAuthorizationCode()`', + ); + } + }); + + it('should throw an error if the model does not implement `revokeAuthorizationCode()`', () => { + try { + const model = { + getAuthorizationCode: () => {}, + }; + + new AuthorizationCodeGrantType({ accessTokenLifetime: 3600, model }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `revokeAuthorizationCode()`', + ); + } + }); + + it('should throw an error if the model does not implement `saveToken()`', () => { + try { + const model = { + getAuthorizationCode: () => {}, + revokeAuthorizationCode: () => {}, + }; + + new AuthorizationCodeGrantType({ accessTokenLifetime: 3600, model }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `saveToken()`', + ); + } + }); + }); + + describe('handle()', () => { + it('should throw an error if `request` is missing', async () => { + const model = { + getAuthorizationCode: () => {}, + revokeAuthorizationCode: () => {}, + saveToken: () => {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + + try { + await grantType.handle(undefined, undefined); + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `request`'); + } + }); + + it('should throw an error if `client` is invalid', () => { + const client = {}; + const model = { + getAuthorizationCode() { + return { + authorizationCode: 12345, + expiresAt: new Date(new Date().getTime() * 2), + user: {}, + }; + }, + revokeAuthorizationCode() {}, + saveToken() {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: {}, + query: {}, + }); + + return grantType + .handle(request, client) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(ServerError); + e.message.should.equal( + 'Server error: `getAuthorizationCode()` did not return a `client` object', + ); + }); + }); + + it('should throw an error if `client` is missing', async () => { + const model = { + getAuthorizationCode: () => { + return { + authorizationCode: 12345, + expiresAt: new Date(new Date().getTime() * 2), + user: {}, + }; + }, + revokeAuthorizationCode: () => {}, + saveToken: () => {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: {}, + query: {}, + }); + + try { + await grantType.handle(request, undefined); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `client`'); + } + }); + + it('should return a token', async () => { + const client = { id: 'foobar' }; + const token = {}; + const model = { + getAuthorizationCode: () => { + return { + authorizationCode: 12345, + client: { id: 'foobar' }, + expiresAt: new Date(new Date().getTime() * 2), + user: {}, + }; + }, + revokeAuthorizationCode: () => { + return true; + }, + saveToken: () => { + return token; + }, + validateScope: () => { + return 'foo'; + }, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: {}, + query: {}, + }); + try { + const data = await grantType.handle(request, client); + data.should.equal(token); + } catch (e) { + should.fail('should.fail', ''); + } + }); + + it('should support promises', () => { + const client = { id: 'foobar' }; + const model = { + getAuthorizationCode: () => { + return Promise.resolve({ + authorizationCode: 12345, + client: { id: 'foobar' }, + expiresAt: new Date(new Date().getTime() * 2), + user: {}, + }); + }, + revokeAuthorizationCode: () => { + return true; + }, + saveToken: () => {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: {}, + query: {}, + }); + + grantType.handle(request, client).should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const client = { id: 'foobar' }; + const model = { + getAuthorizationCode: () => { + return { + authorizationCode: 12345, + client: { id: 'foobar' }, + expiresAt: new Date(new Date().getTime() * 2), + user: {}, + }; + }, + revokeAuthorizationCode: () => { + return true; + }, + saveToken: () => {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: {}, + query: {}, + }); + + grantType.handle(request, client).should.be.an.instanceOf(Promise); + }); + + // it('should support callbacks', () => { + // const client = { id: 'foobar' }; + // const model = { + // getAuthorizationCode: (code, callback) => { + // callback(undefined, { + // authorizationCode: 12345, + // client: { id: 'foobar' }, + // expiresAt: new Date(new Date().getTime() * 2), + // user: {}, + // }); + // }, + // revokeAuthorizationCode: (code, callback) => { + // callback(undefined, { + // authorizationCode: 12345, + // client: { id: 'foobar' }, + // expiresAt: new Date(new Date().getTime() / 2), + // user: {}, + // }); + // }, + // saveToken: (tokenToSave, client, user, callback) => { + // callback(undefined, tokenToSave); + // }, + // }; + // const grantType = new AuthorizationCodeGrantType({ + // accessTokenLifetime: 123, + // model, + // }); + // const request = new Request({ + // body: { code: 12345 }, + // headers: {}, + // method: {}, + // query: {}, + // }); + + // grantType.handle(request, client).should.be.an.instanceOf(Promise); + // }); + }); + + describe('getAuthorizationCode()', () => { + it('should throw an error if the request body does not contain `code`', async () => { + const client = {}; + const model = { + getAuthorizationCode: () => {}, + revokeAuthorizationCode: () => {}, + saveToken: () => {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: {}, + }); + + try { + await grantType.getAuthorizationCode(request, client); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Missing parameter: `code`'); + } + }); + + it('should throw an error if `code` is invalid', async () => { + const client = {}; + const model = { + getAuthorizationCode: () => {}, + revokeAuthorizationCode: () => {}, + saveToken: () => {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 'øå€£‰' }, + headers: {}, + method: {}, + query: {}, + }); + + try { + await grantType.getAuthorizationCode(request, client); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Invalid parameter: `code`'); + } + }); + + it('should throw an error if `authorizationCode` is missing', () => { + const client = {}; + const model = { + getAuthorizationCode: () => {}, + revokeAuthorizationCode: () => {}, + saveToken: () => {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: {}, + query: {}, + }); + + return grantType + .getAuthorizationCode(request, client) + .then(() => should.fail('should.fail', '')) + .catch(e => { + e.should.be.an.instanceOf(InvalidGrantError); + e.message.should.equal( + 'Invalid grant: authorization code is invalid', + ); + }); + }); + + it('should throw an error if `authorizationCode.client` is missing', () => { + const client = {}; + const model = { + getAuthorizationCode: () => { + return { authorizationCode: 12345 }; + }, + revokeAuthorizationCode: () => {}, + saveToken: () => {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: {}, + query: {}, + }); + + return grantType + .getAuthorizationCode(request, client) + .then(() => should.fail('should.fail', '')) + .catch(e => { + e.should.be.an.instanceOf(ServerError); + e.message.should.equal( + 'Server error: `getAuthorizationCode()` did not return a `client` object', + ); + }); + }); + + it('should throw an error if `authorizationCode.expiresAt` is missing', () => { + const client = {}; + const model = { + getAuthorizationCode: () => { + return { authorizationCode: 12345, client: {}, user: {} }; + }, + revokeAuthorizationCode: () => {}, + saveToken: () => {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: {}, + query: {}, + }); + + return grantType + .getAuthorizationCode(request, client) + .then(() => should.fail('should.fail', '')) + .catch(e => { + e.should.be.an.instanceOf(ServerError); + e.message.should.equal( + 'Server error: `expiresAt` must be a Date instance', + ); + }); + }); + + it('should throw an error if `authorizationCode.user` is missing', () => { + const client = {}; + const model = { + getAuthorizationCode: () => { + return { + authorizationCode: 12345, + client: {}, + expiresAt: new Date(), + }; + }, + revokeAuthorizationCode: () => {}, + saveToken: () => {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: {}, + query: {}, + }); + + return grantType + .getAuthorizationCode(request, client) + .then(() => should.fail('should.fail', '')) + .catch(e => { + e.should.be.an.instanceOf(ServerError); + e.message.should.equal( + 'Server error: `getAuthorizationCode()` did not return a `user` object', + ); + }); + }); + + it('should throw an error if the client id does not match', () => { + const client = { id: 123 }; + const model = { + getAuthorizationCode() { + return { + authorizationCode: 12345, + expiresAt: new Date(), + client: { id: 456 }, + user: {}, + }; + }, + revokeAuthorizationCode() {}, + saveToken() {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: {}, + query: {}, + }); + + return grantType + .getAuthorizationCode(request, client) + .then(() => should.fail('should.fail', '')) + .catch(e => { + e.should.be.an.instanceOf(InvalidGrantError); + e.message.should.equal( + 'Invalid grant: authorization code is invalid', + ); + }); + }); + + it('should throw an error if the auth code is expired', () => { + const client = { id: 123 }; + const date = new Date(new Date().getTime() / 2); + const model = { + getAuthorizationCode() { + return { + authorizationCode: 12345, + client: { id: 123 }, + expiresAt: date, + user: {}, + }; + }, + revokeAuthorizationCode() {}, + saveToken() {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: {}, + query: {}, + }); + + return grantType + .getAuthorizationCode(request, client) + .then(() => should.fail('should.fail', '')) + .catch(e => { + e.should.be.an.instanceOf(InvalidGrantError); + e.message.should.equal( + 'Invalid grant: authorization code has expired', + ); + }); + }); + + it('should throw an error if the `redirectUri` is invalid', () => { + const authorizationCode = { + authorizationCode: 12345, + client: { id: 'foobar' }, + expiresAt: new Date(new Date().getTime() * 2), + redirectUri: 'foobar', + user: {}, + }; + const client = { id: 'foobar' }; + const model = { + getAuthorizationCode() { + return authorizationCode; + }, + revokeAuthorizationCode() {}, + saveToken() {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: {}, + query: {}, + }); + + return grantType + .getAuthorizationCode(request, client) + .then(() => should.fail('should.fail', '')) + .catch(e => { + e.should.be.an.instanceOf(InvalidGrantError); + e.message.should.equal( + 'Invalid grant: `redirect_uri` is not a valid URI', + ); + }); + }); + + it('should return an auth code', () => { + const authorizationCode = { + authorizationCode: 12345, + client: { id: 'foobar' }, + expiresAt: new Date(new Date().getTime() * 2), + user: {}, + }; + const client = { id: 'foobar' }; + const model = { + getAuthorizationCode() { + return authorizationCode; + }, + revokeAuthorizationCode() {}, + saveToken() {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: {}, + query: {}, + }); + + return grantType + .getAuthorizationCode(request, client) + .then(data => { + data.should.equal(authorizationCode); + }) + .catch(() => should.fail('should.fail', '')); + }); + + it('should support promises', () => { + const authorizationCode = { + authorizationCode: 12345, + client: { id: 'foobar' }, + expiresAt: new Date(new Date().getTime() * 2), + user: {}, + }; + const client = { id: 'foobar' }; + const model = { + getAuthorizationCode() { + return Promise.resolve(authorizationCode); + }, + revokeAuthorizationCode() {}, + saveToken() {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: {}, + query: {}, + }); + + grantType + .getAuthorizationCode(request, client) + .should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const authorizationCode = { + authorizationCode: 12345, + client: { id: 'foobar' }, + expiresAt: new Date(new Date().getTime() * 2), + user: {}, + }; + const client = { id: 'foobar' }; + const model = { + getAuthorizationCode() { + return authorizationCode; + }, + revokeAuthorizationCode() {}, + saveToken() {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: {}, + query: {}, + }); + + grantType + .getAuthorizationCode(request, client) + .should.be.an.instanceOf(Promise); + }); + + // it('should support callbacks', () => { + // const authorizationCode = { + // authorizationCode: 12345, + // client: { id: 'foobar' }, + // expiresAt: new Date(new Date().getTime() * 2), + // user: {}, + // }; + // const client = { id: 'foobar' }; + // const model = { + // getAuthorizationCode(code, callback) { + // callback(undefined, authorizationCode); + // }, + // revokeAuthorizationCode() {}, + // saveToken() {}, + // }; + // const grantType = new AuthorizationCodeGrantType({ + // accessTokenLifetime: 123, + // model, + // }); + // const request = new Request({ + // body: { code: 12345 }, + // headers: {}, + // method: {}, + // query: {}, + // }); + + // grantType + // .getAuthorizationCode(request, client) + // .should.be.an.instanceOf(Promise); + // }); + }); + + describe('validateRedirectUri()', () => { + it('should throw an error if `redirectUri` is missing', () => { + const authorizationCode = { + authorizationCode: 12345, + client: {}, + expiresAt: new Date(new Date().getTime() / 2), + redirectUri: 'http://foo.bar', + user: {}, + }; + const model = { + getAuthorizationCode() {}, + revokeAuthorizationCode() { + return authorizationCode; + }, + saveToken() {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: {}, + query: {}, + }); + + try { + grantType.validateRedirectUri(request, authorizationCode); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal( + 'Invalid request: `redirect_uri` is not a valid URI', + ); + } + }); + + it('should throw an error if `redirectUri` is invalid', () => { + const authorizationCode = { + authorizationCode: 12345, + client: {}, + expiresAt: new Date(new Date().getTime() / 2), + redirectUri: 'http://foo.bar', + user: {}, + }; + const model = { + getAuthorizationCode() {}, + revokeAuthorizationCode() { + return true; + }, + saveToken() {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345, redirect_uri: 'http://bar.foo' }, + headers: {}, + method: {}, + query: {}, + }); + + try { + grantType.validateRedirectUri(request, authorizationCode); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Invalid request: `redirect_uri` is invalid'); + } + }); + }); + + describe('revokeAuthorizationCode()', () => { + it('should revoke the auth code', async () => { + const authorizationCode = { + authorizationCode: 12345, + client: {}, + expiresAt: new Date(new Date().getTime() / 2), + user: {}, + }; + const model = { + getAuthorizationCode() {}, + revokeAuthorizationCode() { + return true; + }, + saveToken() {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + try { + const data = await grantType.revokeAuthorizationCode(authorizationCode); + data.should.equal(authorizationCode); + } catch (error) { + should.fail('should.fail', ''); + } + }); + + it('should throw an error when the auth code is invalid', () => { + const authorizationCode = { + authorizationCode: 12345, + client: {}, + expiresAt: new Date(new Date().getTime() / 2), + user: {}, + }; + const model = { + getAuthorizationCode() {}, + revokeAuthorizationCode() { + return false; + }, + saveToken() {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + + return grantType + .revokeAuthorizationCode(authorizationCode) + .then(data => { + data.should.equal(authorizationCode); + }) + .catch(e => { + e.should.be.an.instanceOf(InvalidGrantError); + e.message.should.equal( + 'Invalid grant: authorization code is invalid', + ); + }); + }); + + it('should support promises', () => { + const authorizationCode = { + authorizationCode: 12345, + client: {}, + expiresAt: new Date(new Date().getTime() / 2), + user: {}, + }; + const model = { + getAuthorizationCode() {}, + revokeAuthorizationCode() { + return Promise.resolve(true); + }, + saveToken() {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + + grantType + .revokeAuthorizationCode(authorizationCode) + .should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const authorizationCode = { + authorizationCode: 12345, + client: {}, + expiresAt: new Date(new Date().getTime() / 2), + user: {}, + }; + const model = { + getAuthorizationCode() {}, + revokeAuthorizationCode() { + return authorizationCode; + }, + saveToken() {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + + grantType + .revokeAuthorizationCode(authorizationCode) + .should.be.an.instanceOf(Promise); + }); + + // it('should support callbacks', () => { + // const authorizationCode = { + // authorizationCode: 12345, + // client: {}, + // expiresAt: new Date(new Date().getTime() / 2), + // user: {}, + // }; + // const model = { + // getAuthorizationCode() {}, + // revokeAuthorizationCode(code, callback) { + // callback(undefined, authorizationCode); + // }, + // saveToken() {}, + // }; + // const grantType = new AuthorizationCodeGrantType({ + // accessTokenLifetime: 123, + // model, + // }); + + // grantType + // .revokeAuthorizationCode(authorizationCode) + // .should.be.an.instanceOf(Promise); + // }); + }); + + describe('saveToken()', () => { + it('should save the token', async () => { + const token: any = {}; + const model = { + getAuthorizationCode() {}, + revokeAuthorizationCode() {}, + saveToken() { + return token; + }, + validateScope() { + return 'foo'; + }, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + try { + const data = await grantType.saveToken({}, {} as any, token, ''); + data.should.equal(token); + } catch (error) { + should.fail('should.fail', ''); + } + }); + + it('should support promises', () => { + const token: any = {}; + const model = { + getAuthorizationCode() {}, + revokeAuthorizationCode() {}, + saveToken() { + return Promise.resolve(token); + }, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + + grantType + .saveToken({}, {} as any, token, '') + .should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const token: any = {}; + const model = { + getAuthorizationCode() {}, + revokeAuthorizationCode() {}, + saveToken() { + return token; + }, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + + grantType + .saveToken({}, {} as any, token, '') + .should.be.an.instanceOf(Promise); + }); + + /* it('should support callbacks', () => { + const token = {}; + const model = { + getAuthorizationCode() {}, + revokeAuthorizationCode() {}, + saveToken(tokenToSave, client, user, callback) { + callback(undefined, token); + }, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + + grantType.saveToken({}, {}, token, '').should.be.an.instanceOf(Promise); + }); */ + }); +}); diff --git a/test/integration/grant-types/authorization-code-grant-type_test.js b/test/integration/grant-types/authorization-code-grant-type_test.js deleted file mode 100644 index 7f84e3443..000000000 --- a/test/integration/grant-types/authorization-code-grant-type_test.js +++ /dev/null @@ -1,594 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var AuthorizationCodeGrantType = require('../../../lib/grant-types/authorization-code-grant-type'); -var InvalidArgumentError = require('../../../lib/errors/invalid-argument-error'); -var InvalidGrantError = require('../../../lib/errors/invalid-grant-error'); -var InvalidRequestError = require('../../../lib/errors/invalid-request-error'); -var Promise = require('bluebird'); -var Request = require('../../../lib/request'); -var ServerError = require('../../../lib/errors/server-error'); -var should = require('should'); - -/** - * Test `AuthorizationCodeGrantType` integration. - */ - -describe('AuthorizationCodeGrantType integration', function() { - describe('constructor()', function() { - it('should throw an error if `model` is missing', function() { - try { - new AuthorizationCodeGrantType(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `model`'); - } - }); - - it('should throw an error if the model does not implement `getAuthorizationCode()`', function() { - try { - new AuthorizationCodeGrantType({ model: {} }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: model does not implement `getAuthorizationCode()`'); - } - }); - - it('should throw an error if the model does not implement `revokeAuthorizationCode()`', function() { - try { - var model = { - getAuthorizationCode: function() {} - }; - - new AuthorizationCodeGrantType({ model: model }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: model does not implement `revokeAuthorizationCode()`'); - } - }); - - it('should throw an error if the model does not implement `saveToken()`', function() { - try { - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function() {} - }; - - new AuthorizationCodeGrantType({ model: model }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: model does not implement `saveToken()`'); - } - }); - }); - - describe('handle()', function() { - it('should throw an error if `request` is missing', function() { - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - - try { - grantType.handle(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `request`'); - } - }); - - it('should throw an error if `client` is invalid', function() { - var client = {}; - var model = { - getAuthorizationCode: function() { return { authorizationCode: 12345, expiresAt: new Date(new Date() * 2), user: {} }; }, - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - return grantType.handle(request, client) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(ServerError); - e.message.should.equal('Server error: `getAuthorizationCode()` did not return a `client` object'); - }); - }); - - it('should throw an error if `client` is missing', function() { - - var model = { - getAuthorizationCode: function() { return { authorizationCode: 12345, expiresAt: new Date(new Date() * 2), user: {} }; }, - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - try { - grantType.handle(request, null); - } - catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `client`'); - } - }); - - it('should return a token', function() { - var client = { id: 'foobar' }; - var token = {}; - var model = { - getAuthorizationCode: function() { return { authorizationCode: 12345, client: { id: 'foobar' }, expiresAt: new Date(new Date() * 2), user: {} }; }, - revokeAuthorizationCode: function() { return true; }, - saveToken: function() { return token; }, - validateScope: function() { return 'foo'; } - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - return grantType.handle(request, client) - .then(function(data) { - data.should.equal(token); - }) - .catch(should.fail); - }); - - it('should support promises', function() { - var client = { id: 'foobar' }; - var model = { - getAuthorizationCode: function() { return Promise.resolve({ authorizationCode: 12345, client: { id: 'foobar' }, expiresAt: new Date(new Date() * 2), user: {} }); }, - revokeAuthorizationCode: function() { return true; }, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - grantType.handle(request, client).should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var client = { id: 'foobar' }; - var model = { - getAuthorizationCode: function() { return { authorizationCode: 12345, client: { id: 'foobar' }, expiresAt: new Date(new Date() * 2), user: {} }; }, - revokeAuthorizationCode: function() { return true; }, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - grantType.handle(request, client).should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function() { - var client = { id: 'foobar' }; - var model = { - getAuthorizationCode: function(code, callback) { callback(null, { authorizationCode: 12345, client: { id: 'foobar' }, expiresAt: new Date(new Date() * 2), user: {} }); }, - revokeAuthorizationCode: function(code, callback) { callback(null, { authorizationCode: 12345, client: { id: 'foobar' }, expiresAt: new Date(new Date() / 2), user: {} }); }, - saveToken: function(tokenToSave, client, user, callback) { callback(null, tokenToSave); } - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - grantType.handle(request, client).should.be.an.instanceOf(Promise); - }); - }); - - describe('getAuthorizationCode()', function() { - it('should throw an error if the request body does not contain `code`', function() { - var client = {}; - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - try { - grantType.getAuthorizationCode(request, client); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Missing parameter: `code`'); - } - }); - - it('should throw an error if `code` is invalid', function() { - var client = {}; - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 'øå€£‰' }, headers: {}, method: {}, query: {} }); - - try { - grantType.getAuthorizationCode(request, client); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Invalid parameter: `code`'); - } - }); - - it('should throw an error if `authorizationCode` is missing', function() { - var client = {}; - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - return grantType.getAuthorizationCode(request, client) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidGrantError); - e.message.should.equal('Invalid grant: authorization code is invalid'); - }); - }); - - it('should throw an error if `authorizationCode.client` is missing', function() { - var client = {}; - var model = { - getAuthorizationCode: function() { return { authorizationCode: 12345 }; }, - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - return grantType.getAuthorizationCode(request, client) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(ServerError); - e.message.should.equal('Server error: `getAuthorizationCode()` did not return a `client` object'); - }); - }); - - it('should throw an error if `authorizationCode.expiresAt` is missing', function() { - var client = {}; - var model = { - getAuthorizationCode: function() { return { authorizationCode: 12345, client: {}, user: {} }; }, - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - return grantType.getAuthorizationCode(request, client) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(ServerError); - e.message.should.equal('Server error: `expiresAt` must be a Date instance'); - }); - }); - - it('should throw an error if `authorizationCode.user` is missing', function() { - var client = {}; - var model = { - getAuthorizationCode: function() { return { authorizationCode: 12345, client: {}, expiresAt: new Date() }; }, - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - return grantType.getAuthorizationCode(request, client) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(ServerError); - e.message.should.equal('Server error: `getAuthorizationCode()` did not return a `user` object'); - }); - }); - - it('should throw an error if the client id does not match', function() { - var client = { id: 123 }; - var model = { - getAuthorizationCode: function() { - return { authorizationCode: 12345, expiresAt: new Date(), client: { id: 456 }, user: {} }; - }, - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - return grantType.getAuthorizationCode(request, client) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidGrantError); - e.message.should.equal('Invalid grant: authorization code is invalid'); - }); - }); - - it('should throw an error if the auth code is expired', function() { - var client = { id: 123 }; - var date = new Date(new Date() / 2); - var model = { - getAuthorizationCode: function() { - return { authorizationCode: 12345, client: { id: 123 }, expiresAt: date, user: {} }; - }, - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - return grantType.getAuthorizationCode(request, client) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidGrantError); - e.message.should.equal('Invalid grant: authorization code has expired'); - }); - }); - - it('should throw an error if the `redirectUri` is invalid', function() { - var authorizationCode = { authorizationCode: 12345, client: { id: 'foobar' }, expiresAt: new Date(new Date() * 2), redirectUri: 'foobar', user: {} }; - var client = { id: 'foobar' }; - var model = { - getAuthorizationCode: function() { return authorizationCode; }, - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - return grantType.getAuthorizationCode(request, client) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidGrantError); - e.message.should.equal('Invalid grant: `redirect_uri` is not a valid URI'); - }); - }); - - it('should return an auth code', function() { - var authorizationCode = { authorizationCode: 12345, client: { id: 'foobar' }, expiresAt: new Date(new Date() * 2), user: {} }; - var client = { id: 'foobar' }; - var model = { - getAuthorizationCode: function() { return authorizationCode; }, - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - return grantType.getAuthorizationCode(request, client) - .then(function(data) { - data.should.equal(authorizationCode); - }) - .catch(should.fail); - }); - - it('should support promises', function() { - var authorizationCode = { authorizationCode: 12345, client: { id: 'foobar' }, expiresAt: new Date(new Date() * 2), user: {} }; - var client = { id: 'foobar' }; - var model = { - getAuthorizationCode: function() { return Promise.resolve(authorizationCode); }, - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - grantType.getAuthorizationCode(request, client).should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var authorizationCode = { authorizationCode: 12345, client: { id: 'foobar' }, expiresAt: new Date(new Date() * 2), user: {} }; - var client = { id: 'foobar' }; - var model = { - getAuthorizationCode: function() { return authorizationCode; }, - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - grantType.getAuthorizationCode(request, client).should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function() { - var authorizationCode = { authorizationCode: 12345, client: { id: 'foobar' }, expiresAt: new Date(new Date() * 2), user: {} }; - var client = { id: 'foobar' }; - var model = { - getAuthorizationCode: function(code, callback) { callback(null, authorizationCode); }, - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - grantType.getAuthorizationCode(request, client).should.be.an.instanceOf(Promise); - }); - }); - - describe('validateRedirectUri()', function() { - it('should throw an error if `redirectUri` is missing', function() { - var authorizationCode = { authorizationCode: 12345, client: {}, expiresAt: new Date(new Date() / 2), redirectUri: 'http://foo.bar', user: {} }; - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function() { return authorizationCode; }, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - try { - grantType.validateRedirectUri(request, authorizationCode); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Invalid request: `redirect_uri` is not a valid URI'); - } - }); - - it('should throw an error if `redirectUri` is invalid', function() { - var authorizationCode = { authorizationCode: 12345, client: {}, expiresAt: new Date(new Date() / 2), redirectUri: 'http://foo.bar', user: {} }; - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function() { return true; }, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345, redirect_uri: 'http://bar.foo' }, headers: {}, method: {}, query: {} }); - - try { - grantType.validateRedirectUri(request, authorizationCode); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Invalid request: `redirect_uri` is invalid'); - } - }); - }); - - describe('revokeAuthorizationCode()', function() { - it('should revoke the auth code', function() { - var authorizationCode = { authorizationCode: 12345, client: {}, expiresAt: new Date(new Date() / 2), user: {} }; - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function() { return true; }, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - - return grantType.revokeAuthorizationCode(authorizationCode) - .then(function(data) { - data.should.equal(authorizationCode); - }) - .catch(should.fail); - }); - - it('should throw an error when the auth code is invalid', function() { - var authorizationCode = { authorizationCode: 12345, client: {}, expiresAt: new Date(new Date() / 2), user: {} }; - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function() { return false; }, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - - return grantType.revokeAuthorizationCode(authorizationCode) - .then(function(data) { - data.should.equal(authorizationCode); - }) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidGrantError); - e.message.should.equal('Invalid grant: authorization code is invalid'); - }); - }); - - it('should support promises', function() { - var authorizationCode = { authorizationCode: 12345, client: {}, expiresAt: new Date(new Date() / 2), user: {} }; - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function() { return Promise.resolve(true); }, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.revokeAuthorizationCode(authorizationCode).should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var authorizationCode = { authorizationCode: 12345, client: {}, expiresAt: new Date(new Date() / 2), user: {} }; - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function() { return authorizationCode; }, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.revokeAuthorizationCode(authorizationCode).should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function() { - var authorizationCode = { authorizationCode: 12345, client: {}, expiresAt: new Date(new Date() / 2), user: {} }; - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function(code, callback) { callback(null, authorizationCode); }, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.revokeAuthorizationCode(authorizationCode).should.be.an.instanceOf(Promise); - }); - }); - - describe('saveToken()', function() { - it('should save the token', function() { - var token = {}; - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function() {}, - saveToken: function() { return token; }, - validateScope: function() { return 'foo'; } - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - - return grantType.saveToken(token) - .then(function(data) { - data.should.equal(token); - }) - .catch(should.fail); - }); - - it('should support promises', function() { - var token = {}; - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function() {}, - saveToken: function() { return Promise.resolve(token); } - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.saveToken(token).should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var token = {}; - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function() {}, - saveToken: function() { return token; } - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.saveToken(token).should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function() { - var token = {}; - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function() {}, - saveToken: function(tokenToSave, client, user, callback) { callback(null, token); } - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.saveToken(token).should.be.an.instanceOf(Promise); - }); - }); -}); diff --git a/test/integration/grant-types/client-credentials-grant-type.spec.ts b/test/integration/grant-types/client-credentials-grant-type.spec.ts new file mode 100755 index 000000000..99d1dd5ac --- /dev/null +++ b/test/integration/grant-types/client-credentials-grant-type.spec.ts @@ -0,0 +1,381 @@ +import * as should from 'should'; +import { InvalidArgumentError } from '../../../lib/errors/invalid-argument-error'; +import { InvalidGrantError } from '../../../lib/errors/invalid-grant-error'; +import { ClientCredentialsGrantType } from '../../../lib/grant-types/client-credentials-grant-type'; +import { Request } from '../../../lib/request'; + +/** + * Test `ClientCredentialsGrantType` integration. + */ + +describe('ClientCredentialsGrantType integration', () => { + describe('constructor()', () => { + it('should throw an error if `model` is missing', () => { + try { + new ClientCredentialsGrantType({ accessTokenLifetime: 3600 }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `model`'); + } + }); + + it('should throw an error if the model does not implement `getUserFromClient()`', () => { + try { + new ClientCredentialsGrantType({ + accessTokenLifetime: 3600, + model: {}, + }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `getUserFromClient()`', + ); + } + }); + + it('should throw an error if the model does not implement `saveToken()`', () => { + try { + const model = { + getUserFromClient() {}, + }; + + new ClientCredentialsGrantType({ accessTokenLifetime: 3600, model }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `saveToken()`', + ); + } + }); + }); + + describe('handle()', () => { + it('should throw an error if `request` is missing', async () => { + const model = { + getUserFromClient() {}, + saveToken() {}, + }; + const grantType = new ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + + try { + await grantType.handle(undefined, undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `request`'); + } + }); + + it('should throw an error if `client` is missing', async () => { + const model = { + getUserFromClient() {}, + saveToken() {}, + }; + const grantType = new ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: {}, + }); + + try { + await grantType.handle(request, undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `client`'); + } + }); + + it('should return a token', () => { + const token = {}; + const model = { + getUserFromClient() { + return {}; + }, + saveToken() { + return token; + }, + validateScope() { + return 'foo'; + }, + }; + const grantType = new ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: {}, + }); + + return grantType + .handle(request, {} as any) + .then(data => { + data.should.equal(token); + }) + .catch(() => should.fail('should.fail', '')); + }); + + it('should support promises', () => { + const token = {}; + const model = { + getUserFromClient() { + return {}; + }, + saveToken() { + return token; + }, + }; + const grantType = new ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: {}, + }); + + grantType.handle(request, {} as any).should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const token = {}; + const model = { + getUserFromClient() { + return {}; + }, + saveToken() { + return token; + }, + }; + const grantType = new ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: {}, + }); + + grantType.handle(request, {} as any).should.be.an.instanceOf(Promise); + }); + }); + + describe('getUserFromClient()', () => { + it('should throw an error if `user` is missing', () => { + const model = { + getUserFromClient() {}, + saveToken() {}, + }; + const grantType = new ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: {}, + }); + + return grantType + .getUserFromClient({} as any) + .then(() => { + should.fail('should.fail', ''); + }) + .catch((e: any) => { + e.should.be.an.instanceOf(InvalidGrantError); + e.message.should.equal('Invalid grant: user credentials are invalid'); + }); + }); + + it('should return a user', async () => { + const user = { email: 'foo@bar.com' }; + const model = { + getUserFromClient() { + return user; + }, + saveToken() {}, + }; + const grantType = new ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: {}, + }); + try { + const data = await grantType.getUserFromClient({} as any); + data.should.equal(user); + } catch (error) { + should.fail('should.fail', ''); + } + }); + + it('should support promises', () => { + const user = { email: 'foo@bar.com' }; + const model = { + getUserFromClient() { + return Promise.resolve(user); + }, + saveToken() {}, + }; + const grantType = new ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: {}, + }); + + grantType.getUserFromClient({} as any).should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const user = { email: 'foo@bar.com' }; + const model = { + getUserFromClient() { + return user; + }, + saveToken() {}, + }; + const grantType = new ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: {}, + }); + + grantType.getUserFromClient({} as any).should.be.an.instanceOf(Promise); + }); + + /* it('should support callbacks', () => { + const user = { email: 'foo@bar.com' }; + const model = { + getUserFromClient(userId, callback) { + callback(null, user); + }, + saveToken() {}, + }; + const grantType = new ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: {}, + }); + + grantType.getUserFromClient({}).should.be.an.instanceOf(Promise); + }); */ + }); + + describe('saveToken()', () => { + it('should save the token', async () => { + const token: any = {}; + const model = { + getUserFromClient() {}, + saveToken() { + return token; + }, + validateScope() { + return 'foo'; + }, + }; + const grantType = new ClientCredentialsGrantType({ + accessTokenLifetime: 123, + model, + }); + try { + const data = await grantType.saveToken({}, {} as any, token); + data.should.equal(token); + } catch (error) { + should.fail('should.fail', ''); + } + }); + + it('should support promises', () => { + const token: any = {}; + const model = { + getUserFromClient() {}, + saveToken() { + return Promise.resolve(token); + }, + }; + const grantType = new ClientCredentialsGrantType({ + accessTokenLifetime: 123, + model, + }); + + grantType + .saveToken({}, {} as any, token) + .should.be.an.instanceOf(Promise); + }); + + /* it('should support non-promises', () => { + const token = {}; + const model = { + getUserFromClient() {}, + saveToken() { + return token; + }, + }; + const grantType = new ClientCredentialsGrantType({ + accessTokenLifetime: 123, + model, + }); + + grantType.saveToken(token).should.be.an.instanceOf(Promise); + }); */ + + /* it('should support callbacks', () => { + const token = {}; + const model = { + getUserFromClient() {}, + saveToken(tokenToSave, client, user, callback) { + callback(null, token); + }, + }; + const grantType = new ClientCredentialsGrantType({ + accessTokenLifetime: 123, + model, + }); + + grantType.saveToken(token).should.be.an.instanceOf(Promise); + }); */ + }); +}); diff --git a/test/integration/grant-types/client-credentials-grant-type_test.js b/test/integration/grant-types/client-credentials-grant-type_test.js deleted file mode 100644 index 15ec9cc0e..000000000 --- a/test/integration/grant-types/client-credentials-grant-type_test.js +++ /dev/null @@ -1,256 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var ClientCredentialsGrantType = require('../../../lib/grant-types/client-credentials-grant-type'); -var InvalidArgumentError = require('../../../lib/errors/invalid-argument-error'); -var InvalidGrantError = require('../../../lib/errors/invalid-grant-error'); -var Promise = require('bluebird'); -var Request = require('../../../lib/request'); -var should = require('should'); - -/** - * Test `ClientCredentialsGrantType` integration. - */ - -describe('ClientCredentialsGrantType integration', function() { - describe('constructor()', function() { - it('should throw an error if `model` is missing', function() { - try { - new ClientCredentialsGrantType(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `model`'); - } - }); - - it('should throw an error if the model does not implement `getUserFromClient()`', function() { - try { - new ClientCredentialsGrantType({ model: {} }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: model does not implement `getUserFromClient()`'); - } - }); - - it('should throw an error if the model does not implement `saveToken()`', function() { - try { - var model = { - getUserFromClient: function() {} - }; - - new ClientCredentialsGrantType({ model: model }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: model does not implement `saveToken()`'); - } - }); - }); - - describe('handle()', function() { - it('should throw an error if `request` is missing', function() { - var model = { - getUserFromClient: function() {}, - saveToken: function() {} - }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); - - try { - grantType.handle(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `request`'); - } - }); - - it('should throw an error if `client` is missing', function() { - var model = { - getUserFromClient: function() {}, - saveToken: function() {} - }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - try { - grantType.handle(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `client`'); - } - }); - - it('should return a token', function() { - var token = {}; - var model = { - getUserFromClient: function() { return {}; }, - saveToken: function() { return token; }, - validateScope: function() { return 'foo'; } - }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - return grantType.handle(request, {}) - .then(function(data) { - data.should.equal(token); - }) - .catch(should.fail); - }); - - it('should support promises', function() { - var token = {}; - var model = { - getUserFromClient: function() { return {}; }, - saveToken: function() { return token; } - }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - grantType.handle(request, {}).should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var token = {}; - var model = { - getUserFromClient: function() { return {}; }, - saveToken: function() { return token; } - }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - grantType.handle(request, {}).should.be.an.instanceOf(Promise); - }); - }); - - describe('getUserFromClient()', function() { - it('should throw an error if `user` is missing', function() { - var model = { - getUserFromClient: function() {}, - saveToken: function() {} - }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - return grantType.getUserFromClient(request, {}) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidGrantError); - e.message.should.equal('Invalid grant: user credentials are invalid'); - }); - }); - - it('should return a user', function() { - var user = { email: 'foo@bar.com' }; - var model = { - getUserFromClient: function() { return user; }, - saveToken: function() {} - }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - return grantType.getUserFromClient(request, {}) - .then(function(data) { - data.should.equal(user); - }) - .catch(should.fail); - }); - - it('should support promises', function() { - var user = { email: 'foo@bar.com' }; - var model = { - getUserFromClient: function() { return Promise.resolve(user); }, - saveToken: function() {} - }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - grantType.getUserFromClient(request, {}).should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var user = { email: 'foo@bar.com' }; - var model = { - getUserFromClient: function() {return user; }, - saveToken: function() {} - }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - grantType.getUserFromClient(request, {}).should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function() { - var user = { email: 'foo@bar.com' }; - var model = { - getUserFromClient: function(userId, callback) { callback(null, user); }, - saveToken: function() {} - }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - grantType.getUserFromClient(request, {}).should.be.an.instanceOf(Promise); - }); - }); - - describe('saveToken()', function() { - it('should save the token', function() { - var token = {}; - var model = { - getUserFromClient: function() {}, - saveToken: function() { return token; }, - validateScope: function() { return 'foo'; } - }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 123, model: model }); - - return grantType.saveToken(token) - .then(function(data) { - data.should.equal(token); - }) - .catch(should.fail); - }); - - it('should support promises', function() { - var token = {}; - var model = { - getUserFromClient: function() {}, - saveToken: function() { return Promise.resolve(token); } - }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.saveToken(token).should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var token = {}; - var model = { - getUserFromClient: function() {}, - saveToken: function() { return token; } - }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.saveToken(token).should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function() { - var token = {}; - var model = { - getUserFromClient: function() {}, - saveToken: function(tokenToSave, client, user, callback) { callback(null, token); } - }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.saveToken(token).should.be.an.instanceOf(Promise); - }); - }); -}); diff --git a/test/integration/grant-types/password-grant-type.spec.ts b/test/integration/grant-types/password-grant-type.spec.ts new file mode 100755 index 000000000..1b7de2723 --- /dev/null +++ b/test/integration/grant-types/password-grant-type.spec.ts @@ -0,0 +1,502 @@ +import * as should from 'should'; +import { InvalidArgumentError } from '../../../lib/errors/invalid-argument-error'; +import { InvalidGrantError } from '../../../lib/errors/invalid-grant-error'; +import { InvalidRequestError } from '../../../lib/errors/invalid-request-error'; +import { PasswordGrantType } from '../../../lib/grant-types/password-grant-type'; +import { Request } from '../../../lib/request'; + +/** + * Test `PasswordGrantType` integration. + */ + +describe('PasswordGrantType integration', () => { + describe('constructor()', () => { + it('should throw an error if `model` is missing', () => { + try { + new PasswordGrantType({ accessTokenLifetime: 3600 }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `model`'); + } + }); + + it('should throw an error if the model does not implement `getUser()`', () => { + try { + new PasswordGrantType({ accessTokenLifetime: 3600, model: {} }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `getUser()`', + ); + } + }); + + it('should throw an error if the model does not implement `saveToken()`', () => { + try { + const model = { + getUser: () => {}, + }; + + new PasswordGrantType({ accessTokenLifetime: 3600, model }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `saveToken()`', + ); + } + }); + }); + + describe('handle()', () => { + it('should throw an error if `request` is missing', async () => { + const model = { + getUser: () => {}, + saveToken: () => {}, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + + try { + await grantType.handle(undefined, undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `request`'); + } + }); + + it('should throw an error if `client` is missing', async () => { + const model = { + getUser: () => {}, + saveToken: () => {}, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + + try { + await grantType.handle({}, undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `client`'); + } + }); + + it('should return a token', () => { + const client = { id: 'foobar' }; + const token = {}; + const model = { + getUser: () => { + return {}; + }, + saveToken: () => { + return token; + }, + validateScope: () => { + return 'baz'; + }, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { username: 'foo', password: 'bar', scope: 'baz' }, + headers: {}, + method: {}, + query: {}, + }); + + return grantType + .handle(request, client) + .then(data => { + data.should.equal(token); + }) + .catch(() => should.fail('should.fail', '')); + }); + + it('should support promises', () => { + const client = { id: 'foobar' }; + const token = {}; + const model = { + getUser() { + return {}; + }, + saveToken() { + return Promise.resolve(token); + }, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { username: 'foo', password: 'bar' }, + headers: {}, + method: {}, + query: {}, + }); + + grantType.handle(request, client).should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const client = { id: 'foobar' }; + const token = {}; + const model = { + getUser() { + return {}; + }, + saveToken() { + return token; + }, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { username: 'foo', password: 'bar' }, + headers: {}, + method: {}, + query: {}, + }); + + grantType.handle(request, client).should.be.an.instanceOf(Promise); + }); + + /* it('should support callbacks', () => { + const client = { id: 'foobar' }; + const token = {}; + const model = { + getUser(username, password, callback) { + callback(null, {}); + }, + saveToken(tokenToSave, client, user, callback) { + callback(null, token); + }, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { username: 'foo', password: 'bar' }, + headers: {}, + method: {}, + query: {}, + }); + + grantType.handle(request, client).should.be.an.instanceOf(Promise); + }); */ + }); + + describe('getUser()', () => { + it('should throw an error if the request body does not contain `username`', async () => { + const model = { + getUser() {}, + saveToken() {}, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: {}, + }); + + try { + await grantType.getUser(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Missing parameter: `username`'); + } + }); + + it('should throw an error if the request body does not contain `password`', async () => { + const model = { + getUser() {}, + saveToken() {}, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { username: 'foo' }, + headers: {}, + method: {}, + query: {}, + }); + + try { + await grantType.getUser(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Missing parameter: `password`'); + } + }); + + it('should throw an error if `username` is invalid', async () => { + const model = { + getUser() {}, + saveToken() {}, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { username: '\r\n', password: 'foobar' }, + headers: {}, + method: {}, + query: {}, + }); + + try { + await grantType.getUser(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Invalid parameter: `username`'); + } + }); + + it('should throw an error if `password` is invalid', async () => { + const model = { + getUser() {}, + saveToken() {}, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { username: 'foobar', password: '\r\n' }, + headers: {}, + method: {}, + query: {}, + }); + + try { + await grantType.getUser(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Invalid parameter: `password`'); + } + }); + + it('should throw an error if `user` is missing', async () => { + const model = { + getUser() {}, + saveToken() {}, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { username: 'foo', password: 'bar' }, + headers: {}, + method: {}, + query: {}, + }); + try { + await grantType.getUser(request); + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidGrantError); + e.message.should.equal('Invalid grant: user credentials are invalid'); + } + }); + + it('should return a user', async () => { + const user = { email: 'foo@bar.com' }; + const model = { + getUser() { + return user; + }, + saveToken() {}, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { username: 'foo', password: 'bar' }, + headers: {}, + method: {}, + query: {}, + }); + try { + const data = await grantType.getUser(request); + data.should.equal(user); + } catch (error) { + should.fail('should.fail', ''); + } + }); + + it('should support promises', () => { + const user = { email: 'foo@bar.com' }; + const model = { + getUser() { + return Promise.resolve(user); + }, + saveToken() {}, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { username: 'foo', password: 'bar' }, + headers: {}, + method: {}, + query: {}, + }); + + grantType.getUser(request).should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const user = { email: 'foo@bar.com' }; + const model = { + getUser() { + return user; + }, + saveToken() {}, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { username: 'foo', password: 'bar' }, + headers: {}, + method: {}, + query: {}, + }); + + grantType.getUser(request).should.be.an.instanceOf(Promise); + }); + + /* it('should support callbacks', () => { + const user = { email: 'foo@bar.com' }; + const model = { + getUser(username, password, callback) { + callback(null, user); + }, + saveToken() {}, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { username: 'foo', password: 'bar' }, + headers: {}, + method: {}, + query: {}, + }); + + grantType.getUser(request).should.be.an.instanceOf(Promise); + }); */ + }); + + describe('saveToken()', () => { + it('should save the token', async () => { + const token: any = {}; + const model = { + getUser() {}, + saveToken() { + return token; + }, + validateScope() { + return 'foo'; + }, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + try { + const data = await grantType.saveToken({}, {} as any, token); + data.should.equal(token); + } catch (error) { + should.fail('should.fail', ''); + } + }); + + it('should support promises', () => { + const token: any = {}; + const model = { + getUser() {}, + saveToken() { + return Promise.resolve(token); + }, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + + grantType + .saveToken({}, {} as any, token) + .should.be.an.instanceOf(Promise); + }); + + /* it('should support non-promises', () => { + const token = {}; + const model = { + getUser() {}, + saveToken() { + return token; + }, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + + grantType.saveToken(token).should.be.an.instanceOf(Promise); + }); */ + + /* it('should support callbacks', () => { + const token = {}; + const model = { + getUser() {}, + saveToken(tokenToSave, client, user, callback) { + callback(null, token); + }, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + + grantType.saveToken({}, {}, token).should.be.an.instanceOf(Promise); + }); */ + }); +}); diff --git a/test/integration/grant-types/password-grant-type_test.js b/test/integration/grant-types/password-grant-type_test.js deleted file mode 100644 index 9cdf50dd5..000000000 --- a/test/integration/grant-types/password-grant-type_test.js +++ /dev/null @@ -1,344 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var InvalidArgumentError = require('../../../lib/errors/invalid-argument-error'); -var InvalidGrantError = require('../../../lib/errors/invalid-grant-error'); -var InvalidRequestError = require('../../../lib/errors/invalid-request-error'); -var PasswordGrantType = require('../../../lib/grant-types/password-grant-type'); -var Promise = require('bluebird'); -var Request = require('../../../lib/request'); -var should = require('should'); - -/** - * Test `PasswordGrantType` integration. - */ - -describe('PasswordGrantType integration', function() { - describe('constructor()', function() { - it('should throw an error if `model` is missing', function() { - try { - new PasswordGrantType(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `model`'); - } - }); - - it('should throw an error if the model does not implement `getUser()`', function() { - try { - new PasswordGrantType({ model: {} }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: model does not implement `getUser()`'); - } - }); - - it('should throw an error if the model does not implement `saveToken()`', function() { - try { - var model = { - getUser: function() {} - }; - - new PasswordGrantType({ model: model }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: model does not implement `saveToken()`'); - } - }); - }); - - describe('handle()', function() { - it('should throw an error if `request` is missing', function() { - var model = { - getUser: function() {}, - saveToken: function() {} - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - - try { - grantType.handle(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `request`'); - } - }); - - it('should throw an error if `client` is missing', function() { - var model = { - getUser: function() {}, - saveToken: function() {} - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - - try { - grantType.handle({}); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `client`'); - } - }); - - it('should return a token', function() { - var client = { id: 'foobar' }; - var token = {}; - var model = { - getUser: function() { return {}; }, - saveToken: function() { return token; }, - validateScope: function() { return 'baz'; } - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { username: 'foo', password: 'bar', scope: 'baz' }, headers: {}, method: {}, query: {} }); - - return grantType.handle(request, client) - .then(function(data) { - data.should.equal(token); - }) - .catch(should.fail); - }); - - it('should support promises', function() { - var client = { id: 'foobar' }; - var token = {}; - var model = { - getUser: function() { return {}; }, - saveToken: function() { return Promise.resolve(token); } - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { username: 'foo', password: 'bar' }, headers: {}, method: {}, query: {} }); - - grantType.handle(request, client).should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var client = { id: 'foobar' }; - var token = {}; - var model = { - getUser: function() { return {}; }, - saveToken: function() { return token; } - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { username: 'foo', password: 'bar' }, headers: {}, method: {}, query: {} }); - - grantType.handle(request, client).should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function() { - var client = { id: 'foobar' }; - var token = {}; - var model = { - getUser: function(username, password, callback) { callback(null, {}); }, - saveToken: function(tokenToSave, client, user, callback) { callback(null, token); } - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { username: 'foo', password: 'bar' }, headers: {}, method: {}, query: {} }); - - grantType.handle(request, client).should.be.an.instanceOf(Promise); - }); - }); - - describe('getUser()', function() { - it('should throw an error if the request body does not contain `username`', function() { - var model = { - getUser: function() {}, - saveToken: function() {} - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - try { - grantType.getUser(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Missing parameter: `username`'); - } - }); - - it('should throw an error if the request body does not contain `password`', function() { - var model = { - getUser: function() {}, - saveToken: function() {} - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { username: 'foo' }, headers: {}, method: {}, query: {} }); - - try { - grantType.getUser(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Missing parameter: `password`'); - } - }); - - it('should throw an error if `username` is invalid', function() { - var model = { - getUser: function() {}, - saveToken: function() {} - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { username: '\r\n', password: 'foobar' }, headers: {}, method: {}, query: {} }); - - try { - grantType.getUser(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Invalid parameter: `username`'); - } - }); - - it('should throw an error if `password` is invalid', function() { - var model = { - getUser: function() {}, - saveToken: function() {} - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { username: 'foobar', password: '\r\n' }, headers: {}, method: {}, query: {} }); - - try { - grantType.getUser(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Invalid parameter: `password`'); - } - }); - - it('should throw an error if `user` is missing', function() { - var model = { - getUser: function() {}, - saveToken: function() {} - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { username: 'foo', password: 'bar' }, headers: {}, method: {}, query: {} }); - - return grantType.getUser(request) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidGrantError); - e.message.should.equal('Invalid grant: user credentials are invalid'); - }); - }); - - it('should return a user', function() { - var user = { email: 'foo@bar.com' }; - var model = { - getUser: function() { return user; }, - saveToken: function() {} - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { username: 'foo', password: 'bar' }, headers: {}, method: {}, query: {} }); - - return grantType.getUser(request) - .then(function(data) { - data.should.equal(user); - }) - .catch(should.fail); - }); - - it('should support promises', function() { - var user = { email: 'foo@bar.com' }; - var model = { - getUser: function() { return Promise.resolve(user); }, - saveToken: function() {} - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { username: 'foo', password: 'bar' }, headers: {}, method: {}, query: {} }); - - grantType.getUser(request).should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var user = { email: 'foo@bar.com' }; - var model = { - getUser: function() { return user; }, - saveToken: function() {} - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { username: 'foo', password: 'bar' }, headers: {}, method: {}, query: {} }); - - grantType.getUser(request).should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function() { - var user = { email: 'foo@bar.com' }; - var model = { - getUser: function(username, password, callback) { callback(null, user); }, - saveToken: function() {} - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { username: 'foo', password: 'bar' }, headers: {}, method: {}, query: {} }); - - grantType.getUser(request).should.be.an.instanceOf(Promise); - }); - }); - - describe('saveToken()', function() { - it('should save the token', function() { - var token = {}; - var model = { - getUser: function() {}, - saveToken: function() { return token; }, - validateScope: function() { return 'foo'; } - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - - return grantType.saveToken(token) - .then(function(data) { - data.should.equal(token); - }) - .catch(should.fail); - }); - - it('should support promises', function() { - var token = {}; - var model = { - getUser: function() {}, - saveToken: function() { return Promise.resolve(token); } - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.saveToken(token).should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var token = {}; - var model = { - getUser: function() {}, - saveToken: function() { return token; } - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.saveToken(token).should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function() { - var token = {}; - var model = { - getUser: function() {}, - saveToken: function(tokenToSave, client, user, callback) { callback(null, token); } - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.saveToken(token).should.be.an.instanceOf(Promise); - }); - }); -}); diff --git a/test/integration/grant-types/refresh-token-grant-type.spec.ts b/test/integration/grant-types/refresh-token-grant-type.spec.ts new file mode 100755 index 000000000..967f08e15 --- /dev/null +++ b/test/integration/grant-types/refresh-token-grant-type.spec.ts @@ -0,0 +1,840 @@ +import * as should from 'should'; +import { InvalidArgumentError } from '../../../lib/errors/invalid-argument-error'; +import { InvalidGrantError } from '../../../lib/errors/invalid-grant-error'; +import { InvalidRequestError } from '../../../lib/errors/invalid-request-error'; +import { ServerError } from '../../../lib/errors/server-error'; +import { RefreshTokenGrantType } from '../../../lib/grant-types/refresh-token-grant-type'; +import { Request } from '../../../lib/request'; + +/** + * Test `RefreshTokenGrantType` integration. + */ + +describe('RefreshTokenGrantType integration', () => { + describe('constructor()', () => { + it('should throw an error if `model` is missing', () => { + try { + new RefreshTokenGrantType({ accessTokenLifetime: 3600 }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `model`'); + } + }); + + it('should throw an error if the model does not implement `getRefreshToken()`', () => { + try { + new RefreshTokenGrantType({ accessTokenLifetime: 3600, model: {} }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `getRefreshToken()`', + ); + } + }); + + it('should throw an error if the model does not implement `revokeToken()`', () => { + try { + const model = { + async getRefreshToken() {}, + }; + + new RefreshTokenGrantType({ accessTokenLifetime: 3600, model }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `revokeToken()`', + ); + } + }); + + it('should throw an error if the model does not implement `saveToken()`', () => { + try { + const model = { + async getRefreshToken() {}, + async revokeToken() {}, + }; + + new RefreshTokenGrantType({ accessTokenLifetime: 3600, model }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `saveToken()`', + ); + } + }); + }); + + describe('handle()', () => { + it('should throw an error if `request` is missing', async () => { + const model = { + getRefreshToken: () => {}, + revokeToken: () => {}, + saveToken: () => {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + + try { + await grantType.handle(undefined, undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `request`'); + } + }); + + it('should throw an error if `client` is missing', async () => { + const model = { + getRefreshToken: () => {}, + revokeToken: () => {}, + saveToken: () => {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: {}, + }); + + try { + await grantType.handle(request, undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `client`'); + } + }); + + it('should return a token', () => { + const client: any = { id: 123 }; + const token: any = { accessToken: 'foo', client: { id: 123 }, user: {} }; + const model = { + getRefreshToken: () => { + return token; + }, + revokeToken: () => { + return { + accessToken: 'foo', + client: { id: 123 }, + refreshTokenExpiresAt: new Date(new Date().getTime() / 2), + user: {}, + }; + }, + saveToken: () => { + return token; + }, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { refresh_token: 'foobar' }, + headers: {}, + method: {}, + query: {}, + }); + + return grantType + .handle(request, client) + .then(data => { + data.should.equal(token); + }) + .catch(() => should.fail('should.fail', '')); + }); + + it('should support promises', () => { + const client: any = { id: 123 }; + const model = { + getRefreshToken() { + return Promise.resolve({ + accessToken: 'foo', + client: { id: 123 }, + user: {}, + }); + }, + revokeToken() { + return Promise.resolve({ + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date().getTime() / 2), + user: {}, + }); + }, + saveToken() { + return Promise.resolve({ accessToken: 'foo', client: {}, user: {} }); + }, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { refresh_token: 'foobar' }, + headers: {}, + method: {}, + query: {}, + }); + + grantType.handle(request, client).should.be.an.instanceOf(Promise); + }); + + /* it('should support non-promises', () => { + const client : any= { id: 123 }; + const model = { + getRefreshToken() { + return { accessToken: 'foo', client: { id: 123 }, user: {} }; + }, + revokeToken() { + return { + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date() / 2), + user: {}, + }; + }, + saveToken() { + return { accessToken: 'foo', client: {}, user: {} }; + }, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { refresh_token: 'foobar' }, + headers: {}, + method: {}, + query: {}, + }); + + grantType.handle(request, client).should.be.an.instanceOf(Promise); + }); */ + + /* it('should support callbacks', () => { + const client : any= { id: 123 }; + const model = { + getRefreshToken(refreshToken, callback) { + callback(null, { accessToken: 'foo', client: { id: 123 }, user: {} }); + }, + revokeToken(refreshToken, callback) { + callback(null, { + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date() / 2), + user: {}, + }); + }, + saveToken(tokenToSave, client, user, callback) { + callback(null, { accessToken: 'foo', client: {}, user: {} }); + }, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { refresh_token: 'foobar' }, + headers: {}, + method: {}, + query: {}, + }); + + grantType.handle(request, client).should.be.an.instanceOf(Promise); + }); */ + }); + + describe('getRefreshToken()', () => { + it('should throw an error if the `refreshToken` parameter is missing from the request body', async () => { + const client: any = {}; + const model = { + getRefreshToken: () => {}, + revokeToken: () => {}, + saveToken: () => {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: {}, + }); + + try { + await grantType.getRefreshToken(request, client); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Missing parameter: `refresh_token`'); + } + }); + + it('should throw an error if `refreshToken` is not found', () => { + const client: any = { id: 123 }; + const model = { + getRefreshToken() { + return; + }, + revokeToken() {}, + saveToken() {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: { refresh_token: '12345' }, + headers: {}, + method: {}, + query: {}, + }); + + return grantType + .getRefreshToken(request, client) + .then(() => should.fail('should.fail', '')) + .catch(e => { + e.should.be.an.instanceOf(InvalidGrantError); + e.message.should.equal('Invalid grant: refresh token is invalid'); + }); + }); + + it('should throw an error if `refreshToken.client` is missing', () => { + const client: any = {}; + const model = { + getRefreshToken() { + return {}; + }, + revokeToken() {}, + saveToken() {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: { refresh_token: 12345 }, + headers: {}, + method: {}, + query: {}, + }); + + return grantType + .getRefreshToken(request, client) + .then(() => should.fail('should.fail', '')) + .catch(e => { + e.should.be.an.instanceOf(ServerError); + e.message.should.equal( + 'Server error: `getRefreshToken()` did not return a `client` object', + ); + }); + }); + + it('should throw an error if `refreshToken.user` is missing', () => { + const client: any = {}; + const model = { + getRefreshToken() { + return { accessToken: 'foo', client: {} }; + }, + revokeToken() {}, + saveToken() {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: { refresh_token: 12345 }, + headers: {}, + method: {}, + query: {}, + }); + + return grantType + .getRefreshToken(request, client) + .then(() => should.fail('should.fail', '')) + .catch(e => { + e.should.be.an.instanceOf(ServerError); + e.message.should.equal( + 'Server error: `getRefreshToken()` did not return a `user` object', + ); + }); + }); + + it('should throw an error if the client id does not match', () => { + const client: any = { id: 123 }; + const model = { + getRefreshToken() { + return { accessToken: 'foo', client: { id: 456 }, user: {} }; + }, + revokeToken() {}, + saveToken() {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: { refresh_token: 12345 }, + headers: {}, + method: {}, + query: {}, + }); + + return grantType + .getRefreshToken(request, client) + .then(() => should.fail('should.fail', '')) + .catch(e => { + e.should.be.an.instanceOf(InvalidGrantError); + e.message.should.equal('Invalid grant: refresh token is invalid'); + }); + }); + + it('should throw an error if `refresh_token` contains invalid characters', async () => { + const client: any = {}; + const model = { + getRefreshToken() { + return { client: { id: 456 }, user: {} }; + }, + revokeToken() {}, + saveToken() {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: { refresh_token: 'øå€£‰' }, + headers: {}, + method: {}, + query: {}, + }); + + try { + await grantType.getRefreshToken(request, client); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Invalid parameter: `refresh_token`'); + } + }); + + it('should throw an error if `refresh_token` is missing', () => { + const client: any = {}; + const model = { + getRefreshToken() { + return { accessToken: 'foo', client: { id: 456 }, user: {} }; + }, + revokeToken() {}, + saveToken() {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: { refresh_token: 12345 }, + headers: {}, + method: {}, + query: {}, + }); + + return grantType + .getRefreshToken(request, client) + .then(() => should.fail('should.fail', '')) + .catch(e => { + e.should.be.an.instanceOf(InvalidGrantError); + e.message.should.equal('Invalid grant: refresh token is invalid'); + }); + }); + + it('should throw an error if `refresh_token` is expired', () => { + const client: any = { id: 123 }; + const date = new Date(new Date().getTime() / 2); + const model = { + getRefreshToken() { + return { + accessToken: 'foo', + client: { id: 123 }, + refreshTokenExpiresAt: date, + user: {}, + }; + }, + revokeToken() {}, + saveToken() {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: { refresh_token: 12345 }, + headers: {}, + method: {}, + query: {}, + }); + + return grantType + .getRefreshToken(request, client) + .then(() => should.fail('should.fail', '')) + .catch(e => { + e.should.be.an.instanceOf(InvalidGrantError); + e.message.should.equal('Invalid grant: refresh token has expired'); + }); + }); + + it('should throw an error if `refreshTokenExpiresAt` is not a date value', () => { + const client: any = { id: 123 }; + const model = { + getRefreshToken() { + return { + accessToken: 'foo', + client: { id: 123 }, + refreshTokenExpiresAt: 'stringValue', + user: {}, + }; + }, + revokeToken() {}, + saveToken() {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: { refresh_token: 12345 }, + headers: {}, + method: {}, + query: {}, + }); + + return grantType + .getRefreshToken(request, client) + .then(() => should.fail('should.fail', '')) + .catch(e => { + e.should.be.an.instanceOf(ServerError); + e.message.should.equal( + 'Server error: `refreshTokenExpiresAt` must be a Date instance', + ); + }); + }); + + it('should return a token', () => { + const client: any = { id: 123 }; + const token: any = { accessToken: 'foo', client: { id: 123 }, user: {} }; + const model = { + getRefreshToken() { + return token; + }, + revokeToken() {}, + saveToken() {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { refresh_token: 'foobar' }, + headers: {}, + method: {}, + query: {}, + }); + + return grantType + .getRefreshToken(request, client) + .then(data => { + data.should.equal(token); + }) + .catch(() => should.fail('should.fail', '')); + }); + + it('should support promises', () => { + const client: any = { id: 123 }; + const token: any = { accessToken: 'foo', client: { id: 123 }, user: {} }; + const model = { + getRefreshToken() { + return Promise.resolve(token); + }, + revokeToken() {}, + saveToken() {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { refresh_token: 'foobar' }, + headers: {}, + method: {}, + query: {}, + }); + + grantType + .getRefreshToken(request, client) + .should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const client: any = { id: 123 }; + const token: any = { accessToken: 'foo', client: { id: 123 }, user: {} }; + const model = { + getRefreshToken() { + return token; + }, + revokeToken() {}, + saveToken() {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { refresh_token: 'foobar' }, + headers: {}, + method: {}, + query: {}, + }); + + grantType + .getRefreshToken(request, client) + .should.be.an.instanceOf(Promise); + }); + + // it('should support callbacks', () => { + // const client : any= { id: 123 }; + // const token : any= { accessToken: 'foo', client: { id: 123 }, user: {} }; + // const model = { + // getRefreshToken(refreshToken, callback) { + // callback(undefined, token); + // }, + // revokeToken() {}, + // saveToken() {}, + // }; + // const grantType = new RefreshTokenGrantType({ + // accessTokenLifetime: 123, + // model, + // }); + // const request = new Request({ + // body: { refresh_token: 'foobar' }, + // headers: {}, + // method: {}, + // query: {}, + // }); + + // grantType + // .getRefreshToken(request, client) + // .should.be.an.instanceOf(Promise); + // }); + }); + + describe('revokeToken()', () => { + it('should throw an error if the `token` is invalid', () => { + const model = { + getRefreshToken() {}, + revokeToken() {}, + saveToken() {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + + grantType + .revokeToken({} as any) + .then(() => should.fail('should.fail', '')) + .catch(e => { + e.should.be.an.instanceOf(InvalidGrantError); + e.message.should.equal('Invalid grant: refresh token is invalid'); + }); + }); + + it('should revoke the token', () => { + const token: any = { + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date().getTime() / 2), + user: {}, + }; + const model = { + getRefreshToken() {}, + revokeToken() { + return token; + }, + saveToken() {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + + return grantType + .revokeToken(token) + .then(data => { + data.should.equal(token); + }) + .catch(() => should.fail('should.fail', '')); + }); + + it('should support promises', () => { + const token: any = { + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date().getTime() / 2), + user: {}, + }; + const model = { + getRefreshToken() {}, + revokeToken() { + return Promise.resolve(token); + }, + saveToken() {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + + grantType.revokeToken(token).should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const token: any = { + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date().getTime() / 2), + user: {}, + }; + const model = { + getRefreshToken() {}, + revokeToken() { + return token; + }, + saveToken() {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + + grantType.revokeToken(token).should.be.an.instanceOf(Promise); + }); + + /* it('should support callbacks', () => { + const token : any= { + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date().getTime() / 2), + user: {}, + }; + const model = { + getRefreshToken() {}, + revokeToken(refreshToken, callback) { + callback(undefined, token); + }, + saveToken() {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + + grantType.revokeToken(token).should.be.an.instanceOf(Promise); + }); */ + }); + + describe('saveToken()', () => { + it('should save the token', async () => { + const token: any = {}; + const model = { + getRefreshToken() {}, + revokeToken() {}, + saveToken() { + return token; + }, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + try { + const data = await grantType.saveToken({}, {} as any, token); + data.should.equal(token); + } catch (error) { + should.fail('should.fail', ''); + } + }); + + it('should support promises', () => { + const token: any = {}; + const model = { + getRefreshToken() {}, + revokeToken() {}, + saveToken() { + return Promise.resolve(token); + }, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + + grantType + .saveToken({}, {} as any, token) + .should.be.an.instanceOf(Promise); + }); + + // it('should support non-promises', () => { + // const token : any= {}; + // const model = { + // getRefreshToken() {}, + // revokeToken() {}, + // saveToken() { + // return token; + // }, + // }; + // const grantType = new RefreshTokenGrantType({ + // accessTokenLifetime: 123, + // model, + // }); + + // grantType.saveToken(token).should.be.an.instanceOf(Promise); + // }); + + // it('should support callbacks', () => { + // const token : any= {}; + // const model = { + // getRefreshToken() {}, + // revokeToken() {}, + // saveToken(tokenToSave, client, user, callback) { + // callback(null, token); + // }, + // }; + // const grantType = new RefreshTokenGrantType({ + // accessTokenLifetime: 123, + // model, + // }); + + // grantType.saveToken(token).should.be.an.instanceOf(Promise); + // }); + }); +}); diff --git a/test/integration/grant-types/refresh-token-grant-type_test.js b/test/integration/grant-types/refresh-token-grant-type_test.js deleted file mode 100644 index 925396afe..000000000 --- a/test/integration/grant-types/refresh-token-grant-type_test.js +++ /dev/null @@ -1,536 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var InvalidArgumentError = require('../../../lib/errors/invalid-argument-error'); -var InvalidGrantError = require('../../../lib/errors/invalid-grant-error'); -var InvalidRequestError = require('../../../lib/errors/invalid-request-error'); -var Promise = require('bluebird'); -var RefreshTokenGrantType = require('../../../lib/grant-types/refresh-token-grant-type'); -var Request = require('../../../lib/request'); -var ServerError = require('../../../lib/errors/server-error'); -var should = require('should'); - -/** - * Test `RefreshTokenGrantType` integration. - */ - -describe('RefreshTokenGrantType integration', function() { - describe('constructor()', function() { - it('should throw an error if `model` is missing', function() { - try { - new RefreshTokenGrantType(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `model`'); - } - }); - - it('should throw an error if the model does not implement `getRefreshToken()`', function() { - try { - new RefreshTokenGrantType({ model: {} }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: model does not implement `getRefreshToken()`'); - } - }); - - it('should throw an error if the model does not implement `revokeToken()`', function() { - try { - var model = { - getRefreshToken: function() {} - }; - - new RefreshTokenGrantType({ model: model }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: model does not implement `revokeToken()`'); - } - }); - - it('should throw an error if the model does not implement `saveToken()`', function() { - try { - var model = { - getRefreshToken: function() {}, - revokeToken: function() {} - }; - - new RefreshTokenGrantType({ model: model }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: model does not implement `saveToken()`'); - } - }); - }); - - describe('handle()', function() { - it('should throw an error if `request` is missing', function() { - var model = { - getRefreshToken: function() {}, - revokeToken: function() {}, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - - try { - grantType.handle(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `request`'); - } - }); - - it('should throw an error if `client` is missing', function() { - var model = { - getRefreshToken: function() {}, - revokeToken: function() {}, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - try { - grantType.handle(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `client`'); - } - }); - - it('should return a token', function() { - var client = { id: 123 }; - var token = { accessToken: 'foo', client: { id: 123 }, user: {} }; - var model = { - getRefreshToken: function() { return token; }, - revokeToken: function() { return { accessToken: 'foo', client: { id: 123 }, refreshTokenExpiresAt: new Date(new Date() / 2), user: {} }; }, - saveToken: function() { return token; } - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { refresh_token: 'foobar' }, headers: {}, method: {}, query: {} }); - - return grantType.handle(request, client) - .then(function(data) { - data.should.equal(token); - }) - .catch(should.fail); - }); - - it('should support promises', function() { - var client = { id: 123 }; - var model = { - getRefreshToken: function() { return Promise.resolve({ accessToken: 'foo', client: { id: 123 }, user: {} }); }, - revokeToken: function() { return Promise.resolve({ accessToken: 'foo', client: {}, refreshTokenExpiresAt: new Date(new Date() / 2), user: {} }); }, - saveToken: function() { return Promise.resolve({ accessToken: 'foo', client: {}, user: {} }); } - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { refresh_token: 'foobar' }, headers: {}, method: {}, query: {} }); - - grantType.handle(request, client).should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var client = { id: 123 }; - var model = { - getRefreshToken: function() { return { accessToken: 'foo', client: { id: 123 }, user: {} }; }, - revokeToken: function() { return { accessToken: 'foo', client: {}, refreshTokenExpiresAt: new Date(new Date() / 2), user: {} }; }, - saveToken: function() { return { accessToken: 'foo', client: {}, user: {} }; } - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { refresh_token: 'foobar' }, headers: {}, method: {}, query: {} }); - - grantType.handle(request, client).should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function() { - var client = { id: 123 }; - var model = { - getRefreshToken: function(refreshToken, callback) { callback(null, { accessToken: 'foo', client: { id: 123 }, user: {} }); }, - revokeToken: function(refreshToken, callback) { callback(null, { accessToken: 'foo', client: {}, refreshTokenExpiresAt: new Date(new Date() / 2), user: {} }); }, - saveToken: function(tokenToSave, client, user, callback) { callback(null,{ accessToken: 'foo', client: {}, user: {} }); } - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { refresh_token: 'foobar' }, headers: {}, method: {}, query: {} }); - - grantType.handle(request, client).should.be.an.instanceOf(Promise); - }); - }); - - describe('getRefreshToken()', function() { - it('should throw an error if the `refreshToken` parameter is missing from the request body', function() { - var client = {}; - var model = { - getRefreshToken: function() {}, - revokeToken: function() {}, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - try { - grantType.getRefreshToken(request, client); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Missing parameter: `refresh_token`'); - } - }); - - it('should throw an error if `refreshToken` is not found', function() { - var client = { id: 123 }; - var model = { - getRefreshToken: function() { return; }, - revokeToken: function() {}, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: { refresh_token: '12345' }, headers: {}, method: {}, query: {} }); - - return grantType.getRefreshToken(request, client) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidGrantError); - e.message.should.equal('Invalid grant: refresh token is invalid'); - }); - }); - - it('should throw an error if `refreshToken.client` is missing', function() { - var client = {}; - var model = { - getRefreshToken: function() { return {}; }, - revokeToken: function() {}, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: { refresh_token: 12345 }, headers: {}, method: {}, query: {} }); - - return grantType.getRefreshToken(request, client) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(ServerError); - e.message.should.equal('Server error: `getRefreshToken()` did not return a `client` object'); - }); - }); - - it('should throw an error if `refreshToken.user` is missing', function() { - var client = {}; - var model = { - getRefreshToken: function() { - return { accessToken: 'foo', client: {} }; - }, - revokeToken: function() {}, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: { refresh_token: 12345 }, headers: {}, method: {}, query: {} }); - - return grantType.getRefreshToken(request, client) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(ServerError); - e.message.should.equal('Server error: `getRefreshToken()` did not return a `user` object'); - }); - }); - - it('should throw an error if the client id does not match', function() { - var client = { id: 123 }; - var model = { - getRefreshToken: function() { - return { accessToken: 'foo', client: { id: 456 }, user: {} }; - }, - revokeToken: function() {}, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: { refresh_token: 12345 }, headers: {}, method: {}, query: {} }); - - return grantType.getRefreshToken(request, client) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidGrantError); - e.message.should.equal('Invalid grant: refresh token is invalid'); - }); - }); - - it('should throw an error if `refresh_token` contains invalid characters', function() { - var client = {}; - var model = { - getRefreshToken: function() { - return { client: { id: 456 }, user: {} }; - }, - revokeToken: function() {}, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: { refresh_token: 'øå€£‰' }, headers: {}, method: {}, query: {} }); - - try { - grantType.getRefreshToken(request, client); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Invalid parameter: `refresh_token`'); - } - }); - - it('should throw an error if `refresh_token` is missing', function() { - var client = {}; - var model = { - getRefreshToken: function() { - return { accessToken: 'foo', client: { id: 456 }, user: {} }; - }, - revokeToken: function() {}, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: { refresh_token: 12345 }, headers: {}, method: {}, query: {} }); - - return grantType.getRefreshToken(request, client) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidGrantError); - e.message.should.equal('Invalid grant: refresh token is invalid'); - }); - }); - - it('should throw an error if `refresh_token` is expired', function() { - var client = { id: 123 }; - var date = new Date(new Date() / 2); - var model = { - getRefreshToken: function() { - return { accessToken: 'foo', client: { id: 123 }, refreshTokenExpiresAt: date, user: {} }; - }, - revokeToken: function() {}, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: { refresh_token: 12345 }, headers: {}, method: {}, query: {} }); - - return grantType.getRefreshToken(request, client) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidGrantError); - e.message.should.equal('Invalid grant: refresh token has expired'); - }); - }); - - it('should throw an error if `refreshTokenExpiresAt` is not a date value', function() { - var client = { id: 123 }; - var model = { - getRefreshToken: function() { - return { accessToken: 'foo', client: { id: 123 }, refreshTokenExpiresAt: 'stringvalue', user: {} }; - }, - revokeToken: function() {}, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: { refresh_token: 12345 }, headers: {}, method: {}, query: {} }); - - return grantType.getRefreshToken(request, client) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(ServerError); - e.message.should.equal('Server error: `refreshTokenExpiresAt` must be a Date instance'); - }); - }); - - it('should return a token', function() { - var client = { id: 123 }; - var token = { accessToken: 'foo', client: { id: 123 }, user: {} }; - var model = { - getRefreshToken: function() { return token; }, - revokeToken: function() {}, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { refresh_token: 'foobar' }, headers: {}, method: {}, query: {} }); - - return grantType.getRefreshToken(request, client) - .then(function(data) { - data.should.equal(token); - }) - .catch(should.fail); - }); - - it('should support promises', function() { - var client = { id: 123 }; - var token = { accessToken: 'foo', client: { id: 123 }, user: {} }; - var model = { - getRefreshToken: function() { return Promise.resolve(token); }, - revokeToken: function() {}, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { refresh_token: 'foobar' }, headers: {}, method: {}, query: {} }); - - grantType.getRefreshToken(request, client).should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var client = { id: 123 }; - var token = { accessToken: 'foo', client: { id: 123 }, user: {} }; - var model = { - getRefreshToken: function() { return token; }, - revokeToken: function() {}, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { refresh_token: 'foobar' }, headers: {}, method: {}, query: {} }); - - grantType.getRefreshToken(request, client).should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function() { - var client = { id: 123 }; - var token = { accessToken: 'foo', client: { id: 123 }, user: {} }; - var model = { - getRefreshToken: function(refreshToken, callback) { callback(null, token); }, - revokeToken: function() {}, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { refresh_token: 'foobar' }, headers: {}, method: {}, query: {} }); - - grantType.getRefreshToken(request, client).should.be.an.instanceOf(Promise); - }); - }); - - describe('revokeToken()', function() { - it('should throw an error if the `token` is invalid', function() { - var model = { - getRefreshToken: function() {}, - revokeToken: function() {}, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - - grantType.revokeToken({}) - .then(should.fail) - .catch(function (e) { - e.should.be.an.instanceOf(InvalidGrantError); - e.message.should.equal('Invalid grant: refresh token is invalid'); - }); - }); - - it('should revoke the token', function() { - var token = { accessToken: 'foo', client: {}, refreshTokenExpiresAt: new Date(new Date() / 2), user: {} }; - var model = { - getRefreshToken: function() {}, - revokeToken: function() { return token; }, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); - - return grantType.revokeToken(token) - .then(function(data) { - data.should.equal(token); - }) - .catch(should.fail); - }); - - it('should support promises', function() { - var token = { accessToken: 'foo', client: {}, refreshTokenExpiresAt: new Date(new Date() / 2), user: {} }; - var model = { - getRefreshToken: function() {}, - revokeToken: function() { return Promise.resolve(token); }, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.revokeToken(token).should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var token = { accessToken: 'foo', client: {}, refreshTokenExpiresAt: new Date(new Date() / 2), user: {} }; - var model = { - getRefreshToken: function() {}, - revokeToken: function() { return token; }, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.revokeToken(token).should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function() { - var token = { accessToken: 'foo', client: {}, refreshTokenExpiresAt: new Date(new Date() / 2), user: {} }; - var model = { - getRefreshToken: function() {}, - revokeToken: function(refreshToken, callback) { callback(null, token); }, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.revokeToken(token).should.be.an.instanceOf(Promise); - }); - }); - - describe('saveToken()', function() { - it('should save the token', function() { - var token = {}; - var model = { - getRefreshToken: function() {}, - revokeToken: function() {}, - saveToken: function() { return token; } - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); - - return grantType.saveToken(token) - .then(function(data) { - data.should.equal(token); - }) - .catch(should.fail); - }); - - it('should support promises', function() { - var token = {}; - var model = { - getRefreshToken: function() {}, - revokeToken: function() {}, - saveToken: function() { return Promise.resolve(token); } - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.saveToken(token).should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var token = {}; - var model = { - getRefreshToken: function() {}, - revokeToken: function() {}, - saveToken: function() { return token; } - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.saveToken(token).should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function() { - var token = {}; - var model = { - getRefreshToken: function() {}, - revokeToken: function() {}, - saveToken: function(tokenToSave, client, user, callback) { callback(null, token); } - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.saveToken(token).should.be.an.instanceOf(Promise); - }); - }); -}); diff --git a/test/integration/handlers/authenticate-handler.spec.ts b/test/integration/handlers/authenticate-handler.spec.ts new file mode 100755 index 000000000..522b9b233 --- /dev/null +++ b/test/integration/handlers/authenticate-handler.spec.ts @@ -0,0 +1,692 @@ +import * as should from 'should'; +import { AccessDeniedError } from '../../../lib/errors/access-denied-error'; +import { InsufficientScopeError } from '../../../lib/errors/insufficient-scope-error'; +import { InvalidArgumentError } from '../../../lib/errors/invalid-argument-error'; +import { InvalidRequestError } from '../../../lib/errors/invalid-request-error'; +import { InvalidTokenError } from '../../../lib/errors/invalid-token-error'; +import { ServerError } from '../../../lib/errors/server-error'; +import { UnauthorizedRequestError } from '../../../lib/errors/unauthorized-request-error'; +import { AuthenticateHandler } from '../../../lib/handlers/authenticate-handler'; +import { Request } from '../../../lib/request'; +import { Response } from '../../../lib/response'; + +/** + * Test `AuthenticateHandler` integration. + */ + +describe('AuthenticateHandler integration', () => { + describe('constructor()', () => { + it('should throw an error if `options.model` is missing', () => { + try { + new AuthenticateHandler(); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `model`'); + } + }); + + it('should throw an error if the model does not implement `getAccessToken()`', () => { + try { + new AuthenticateHandler({ model: {} }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `getAccessToken()`', + ); + } + }); + + it('should throw an error if `scope` was given and `addAcceptedScopesHeader()` is missing', () => { + try { + new AuthenticateHandler({ + model: { getAccessToken() {} }, + scope: 'foobar', + }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `addAcceptedScopesHeader`'); + } + }); + + it('should throw an error if `scope` was given and `addAuthorizedScopesHeader()` is missing', () => { + try { + new AuthenticateHandler({ + addAcceptedScopesHeader: true, + model: { getAccessToken() {} }, + scope: 'foobar', + }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Missing parameter: `addAuthorizedScopesHeader`', + ); + } + }); + + it('should throw an error if `scope` was given and the model does not implement `verifyScope()`', () => { + try { + new AuthenticateHandler({ + addAcceptedScopesHeader: true, + addAuthorizedScopesHeader: true, + model: { getAccessToken() {} }, + scope: 'foobar', + }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `verifyScope()`', + ); + } + }); + + it('should set the `model`', () => { + const model = { getAccessToken() {} }; + const grantType = new AuthenticateHandler({ model }); + + grantType.model.should.equal(model); + }); + + it('should set the `scope`', () => { + const model = { + getAccessToken() {}, + verifyScope() {}, + }; + const grantType = new AuthenticateHandler({ + addAcceptedScopesHeader: true, + addAuthorizedScopesHeader: true, + model, + scope: 'foobar', + }); + + grantType.scope.should.equal('foobar'); + }); + }); + + describe('handle()', () => { + it('should throw an error if `request` is missing', async () => { + const handler = new AuthenticateHandler({ + model: { getAccessToken() {} }, + }); + + try { + await handler.handle(undefined, undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: `request` must be an instance of Request', + ); + } + }); + + it('should set the `WWW-Authenticate` header if an unauthorized request error is thrown', () => { + const model = { + getAccessToken() { + throw new UnauthorizedRequestError(undefined, undefined); + }, + }; + const handler = new AuthenticateHandler({ model }); + const request = new Request({ + body: {}, + headers: { Authorization: 'Bearer foo' }, + method: {}, + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => should.fail('should.fail', '')) + .catch(() => { + response + .get('WWW-Authenticate') + .should.equal('Bearer realm="Service"'); + }); + }); + + it('should throw the error if an oauth error is thrown', () => { + const model = { + getAccessToken() { + throw new AccessDeniedError('Cannot request this access token'); + }, + }; + const handler = new AuthenticateHandler({ model }); + const request = new Request({ + body: {}, + headers: { Authorization: 'Bearer foo' }, + method: {}, + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => should.fail('should.fail', '')) + .catch(e => { + e.should.be.an.instanceOf(AccessDeniedError); + e.message.should.equal('Cannot request this access token'); + }); + }); + + it('should throw a server error if a non-oauth error is thrown', () => { + const model = { + getAccessToken() { + throw new Error('Unhandled exception'); + }, + }; + const handler = new AuthenticateHandler({ model }); + const request = new Request({ + body: {}, + headers: { Authorization: 'Bearer foo' }, + method: {}, + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => should.fail('should.fail', '')) + .catch(e => { + e.should.be.an.instanceOf(ServerError); + e.message.should.equal('Unhandled exception'); + }); + }); + + it('should return an access token', () => { + const accessToken: any = { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + const model = { + getAccessToken() { + return accessToken; + }, + verifyScope() { + return true; + }, + }; + const handler = new AuthenticateHandler({ + addAcceptedScopesHeader: true, + addAuthorizedScopesHeader: true, + model, + scope: 'foo', + }); + const request = new Request({ + body: {}, + headers: { Authorization: 'Bearer foo' }, + method: {}, + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(data => { + data.should.equal(accessToken); + }) + .catch(() => should.fail('should.fail', '')); + }); + }); + + describe('getTokenFromRequest()', () => { + it('should throw an error if more than one authentication method is used', () => { + const handler = new AuthenticateHandler({ + model: { getAccessToken() {} }, + }); + const request = new Request({ + body: {}, + headers: { Authorization: 'Bearer foo' }, + method: {}, + query: { access_token: 'foo' }, + }); + + try { + handler.getTokenFromRequest(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal( + 'Invalid request: only one authentication method is allowed', + ); + } + }); + + it('should throw an error if `accessToken` is missing', () => { + const handler = new AuthenticateHandler({ + model: { getAccessToken() {} }, + }); + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: {}, + }); + + try { + handler.getTokenFromRequest(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(UnauthorizedRequestError); + e.message.should.equal('Unauthorized request: no authentication given'); + } + }); + }); + + describe('getTokenFromRequestHeader()', () => { + it('should throw an error if the token is malformed', () => { + const handler = new AuthenticateHandler({ + model: { getAccessToken() {} }, + }); + const request = new Request({ + body: {}, + headers: { + Authorization: 'foobar', + }, + method: {}, + query: {}, + }); + + try { + handler.getTokenFromRequestHeader(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal( + 'Invalid request: malformed authorization header', + ); + } + }); + + it('should return the bearer token', () => { + const handler = new AuthenticateHandler({ + model: { getAccessToken() {} }, + }); + const request = new Request({ + body: {}, + headers: { + Authorization: 'Bearer foo', + }, + method: {}, + query: {}, + }); + + const bearerToken = handler.getTokenFromRequestHeader(request); + + bearerToken.should.equal('foo'); + }); + }); + + describe('getTokenFromRequestQuery()', () => { + it('should throw an error if the query contains a token', () => { + const handler = new AuthenticateHandler({ + model: { getAccessToken() {} }, + }); + + try { + handler.getTokenFromRequestQuery(undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal( + 'Invalid request: do not send bearer tokens in query URLs', + ); + } + }); + + it('should return the bearer token if `allowBearerTokensInQueryString` is true', () => { + const handler = new AuthenticateHandler({ + allowBearerTokensInQueryString: true, + model: { getAccessToken() {} }, + }); + const req = { query: { access_token: 'foo' } }; + handler.getTokenFromRequestQuery(req as Request).should.equal('foo'); + }); + }); + + describe('getTokenFromRequestBody()', () => { + it('should throw an error if the method is `GET`', () => { + const handler = new AuthenticateHandler({ + model: { getAccessToken() {} }, + }); + const request = new Request({ + body: { access_token: 'foo' }, + headers: {}, + method: 'GET', + query: {}, + }); + + try { + handler.getTokenFromRequestBody(request); + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal( + 'Invalid request: token may not be passed in the body when using the GET verb', + ); + } + }); + + it('should throw an error if the media type is not `application/x-www-form-urlencoded`', () => { + const handler = new AuthenticateHandler({ + model: { getAccessToken() {} }, + }); + const request = new Request({ + body: { access_token: 'foo' }, + headers: {}, + method: {}, + query: {}, + }); + + try { + handler.getTokenFromRequestBody(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal( + 'Invalid request: content must be application/x-www-form-urlencoded', + ); + } + }); + + it('should return the bearer token', () => { + const handler = new AuthenticateHandler({ + model: { getAccessToken() {} }, + }); + const request = new Request({ + body: { access_token: 'foo' }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: {}, + query: {}, + }); + + handler.getTokenFromRequestBody(request).should.equal('foo'); + }); + }); + + describe('getAccessToken()', () => { + it('should throw an error if `accessToken` is missing', () => { + const model = { + getAccessToken() {}, + }; + const handler = new AuthenticateHandler({ model }); + + return handler + .getAccessToken('foo') + .then(() => should.fail('should.fail', '')) + .catch(e => { + e.should.be.an.instanceOf(InvalidTokenError); + e.message.should.equal('Invalid token: access token is invalid'); + }); + }); + + it('should throw an error if `accessToken.user` is missing', () => { + const model = { + getAccessToken() { + return {}; + }, + }; + const handler = new AuthenticateHandler({ model }); + + return handler + .getAccessToken('foo') + .then(() => should.fail('should.fail', '')) + .catch(e => { + e.should.be.an.instanceOf(ServerError); + e.message.should.equal( + 'Server error: `getAccessToken()` did not return a `user` object', + ); + }); + }); + + it('should return an access token', () => { + const accessToken: any = { user: {} }; + const model = { + getAccessToken() { + return accessToken; + }, + }; + const handler = new AuthenticateHandler({ model }); + + return handler + .getAccessToken('foo') + .then(data => { + data.should.equal(accessToken); + }) + .catch(() => should.fail('should.fail', '')); + }); + + it('should support promises', () => { + const model = { + getAccessToken() { + return Promise.resolve({ user: {} }); + }, + }; + const handler = new AuthenticateHandler({ model }); + + handler.getAccessToken('foo').should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const model = { + getAccessToken() { + return { user: {} }; + }, + }; + const handler = new AuthenticateHandler({ model }); + + handler.getAccessToken('foo').should.be.an.instanceOf(Promise); + }); + + /* it('should support callbacks', () => { + const model = { + getAccessToken(token, callback) { + callback(null, { user: {} }); + }, + }; + const handler = new AuthenticateHandler({ model }); + + handler.getAccessToken('foo').should.be.an.instanceOf(Promise); + }); */ + }); + + describe('validateAccessToken()', () => { + it('should throw an error if `accessToken` is expired', () => { + const accessToken: any = { + accessTokenExpiresAt: new Date(new Date().getTime() / 2), + }; + const handler = new AuthenticateHandler({ + model: { getAccessToken() {} }, + }); + + try { + handler.validateAccessToken(accessToken); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidTokenError); + e.message.should.equal('Invalid token: access token has expired'); + } + }); + + it('should return an access token', () => { + const accessToken: any = { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + const handler = new AuthenticateHandler({ + model: { getAccessToken() {} }, + }); + + handler.validateAccessToken(accessToken).should.equal(accessToken); + }); + }); + + describe('verifyScope()', () => { + it('should throw an error if `scope` is insufficient', () => { + const model = { + getAccessToken() {}, + verifyScope() { + return false; + }, + }; + const handler = new AuthenticateHandler({ + addAcceptedScopesHeader: true, + addAuthorizedScopesHeader: true, + model, + scope: 'foo', + }); + + return handler + .verifyScope('foo' as any) + .then(() => should.fail('should.fail', '')) + .catch(e => { + e.should.be.an.instanceOf(InsufficientScopeError); + e.message.should.equal( + 'Insufficient scope: authorized scope is insufficient', + ); + }); + }); + + it('should support promises', () => { + const model = { + getAccessToken() {}, + verifyScope() { + return true; + }, + }; + const handler = new AuthenticateHandler({ + addAcceptedScopesHeader: true, + addAuthorizedScopesHeader: true, + model, + scope: 'foo', + }); + + handler.verifyScope('foo' as any).should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const model = { + getAccessToken() {}, + verifyScope() { + return true; + }, + }; + const handler = new AuthenticateHandler({ + addAcceptedScopesHeader: true, + addAuthorizedScopesHeader: true, + model, + scope: 'foo', + }); + + handler.verifyScope('foo' as any).should.be.an.instanceOf(Promise); + }); + + /* it('should support callbacks', () => { + const model = { + getAccessToken() {}, + verifyScope(token, scope, callback) { + callback(null, true); + }, + }; + const handler = new AuthenticateHandler({ + addAcceptedScopesHeader: true, + addAuthorizedScopesHeader: true, + model, + scope: 'foo', + }); + + handler.verifyScope('foo').should.be.an.instanceOf(Promise); + }); */ + }); + + describe('updateResponse()', () => { + it('should not set the `X-Accepted-OAuth-Scopes` header if `scope` is not specified', () => { + const model = { + getAccessToken() {}, + verifyScope() {}, + }; + const handler = new AuthenticateHandler({ + addAcceptedScopesHeader: true, + addAuthorizedScopesHeader: false, + model, + }); + const response = new Response({ body: {}, headers: {} }); + + handler.updateResponse(response, { scope: 'foo biz' }); + + response.headers.should.not.have.property('x-accepted-oauth-scopes'); + }); + + it('should set the `X-Accepted-OAuth-Scopes` header if `scope` is specified', () => { + const model = { + getAccessToken() {}, + verifyScope() {}, + }; + const handler = new AuthenticateHandler({ + addAcceptedScopesHeader: true, + addAuthorizedScopesHeader: false, + model, + scope: 'foo bar', + }); + const response = new Response({ body: {}, headers: {} }); + + handler.updateResponse(response, { scope: 'foo biz' }); + + response.get('X-Accepted-OAuth-Scopes').should.equal('foo bar'); + }); + + it('should not set the `X-Authorized-OAuth-Scopes` header if `scope` is not specified', () => { + const model = { + getAccessToken() {}, + verifyScope() {}, + }; + const handler = new AuthenticateHandler({ + addAcceptedScopesHeader: false, + addAuthorizedScopesHeader: true, + model, + }); + const response = new Response({ body: {}, headers: {} }); + + handler.updateResponse(response, { scope: 'foo biz' }); + + response.headers.should.not.have.property('x-oauth-scopes'); + }); + + it('should set the `X-Authorized-OAuth-Scopes` header', () => { + const model = { + getAccessToken() {}, + verifyScope() {}, + }; + const handler = new AuthenticateHandler({ + addAcceptedScopesHeader: false, + addAuthorizedScopesHeader: true, + model, + scope: 'foo bar', + }); + const response = new Response({ body: {}, headers: {} }); + + handler.updateResponse(response, { scope: 'foo biz' }); + + response.get('X-OAuth-Scopes').should.equal('foo biz'); + }); + }); +}); diff --git a/test/integration/handlers/authenticate-handler_test.js b/test/integration/handlers/authenticate-handler_test.js deleted file mode 100644 index 7852ea2eb..000000000 --- a/test/integration/handlers/authenticate-handler_test.js +++ /dev/null @@ -1,563 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var AccessDeniedError = require('../../../lib/errors/access-denied-error'); -var AuthenticateHandler = require('../../../lib/handlers/authenticate-handler'); -var InvalidArgumentError = require('../../../lib/errors/invalid-argument-error'); -var InvalidRequestError = require('../../../lib/errors/invalid-request-error'); -var InsufficientScopeError = require('../../../lib/errors/insufficient-scope-error'); -var InvalidTokenError = require('../../../lib/errors/invalid-token-error'); -var Promise = require('bluebird'); -var Request = require('../../../lib/request'); -var Response = require('../../../lib/response'); -var ServerError = require('../../../lib/errors/server-error'); -var UnauthorizedRequestError = require('../../../lib/errors/unauthorized-request-error'); -var should = require('should'); - -/** - * Test `AuthenticateHandler` integration. - */ - -describe('AuthenticateHandler integration', function() { - describe('constructor()', function() { - it('should throw an error if `options.model` is missing', function() { - try { - new AuthenticateHandler(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `model`'); - } - }); - - it('should throw an error if the model does not implement `getAccessToken()`', function() { - try { - new AuthenticateHandler({ model: {} }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: model does not implement `getAccessToken()`'); - } - }); - - it('should throw an error if `scope` was given and `addAcceptedScopesHeader()` is missing', function() { - try { - new AuthenticateHandler({ model: { getAccessToken: function() {} }, scope: 'foobar' }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `addAcceptedScopesHeader`'); - } - }); - - it('should throw an error if `scope` was given and `addAuthorizedScopesHeader()` is missing', function() { - try { - new AuthenticateHandler({ addAcceptedScopesHeader: true, model: { getAccessToken: function() {} }, scope: 'foobar' }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `addAuthorizedScopesHeader`'); - } - }); - - it('should throw an error if `scope` was given and the model does not implement `verifyScope()`', function() { - try { - new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: true, model: { getAccessToken: function() {} }, scope: 'foobar' }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: model does not implement `verifyScope()`'); - } - }); - - it('should set the `model`', function() { - var model = { getAccessToken: function() {} }; - var grantType = new AuthenticateHandler({ model: model }); - - grantType.model.should.equal(model); - }); - - it('should set the `scope`', function() { - var model = { - getAccessToken: function() {}, - verifyScope: function() {} - }; - var grantType = new AuthenticateHandler({ - addAcceptedScopesHeader: true, - addAuthorizedScopesHeader: true, - model: model, - scope: 'foobar' - }); - - grantType.scope.should.equal('foobar'); - }); - }); - - describe('handle()', function() { - it('should throw an error if `request` is missing', function() { - var handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); - - try { - handler.handle(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: `request` must be an instance of Request'); - } - }); - - it('should set the `WWW-Authenticate` header if an unauthorized request error is thrown', function() { - var model = { - getAccessToken: function() { - throw new UnauthorizedRequestError(); - } - }; - var handler = new AuthenticateHandler({ model: model }); - var request = new Request({ body: {}, headers: { 'Authorization': 'Bearer foo' }, method: {}, query: {} }); - var response = new Response({ body: {}, headers: {} }); - - return handler.handle(request, response) - .then(should.fail) - .catch(function() { - response.get('WWW-Authenticate').should.equal('Bearer realm="Service"'); - }); - }); - - it('should throw the error if an oauth error is thrown', function() { - var model = { - getAccessToken: function() { - throw new AccessDeniedError('Cannot request this access token'); - } - }; - var handler = new AuthenticateHandler({ model: model }); - var request = new Request({ body: {}, headers: { 'Authorization': 'Bearer foo' }, method: {}, query: {} }); - var response = new Response({ body: {}, headers: {} }); - - return handler.handle(request, response) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(AccessDeniedError); - e.message.should.equal('Cannot request this access token'); - }); - }); - - it('should throw a server error if a non-oauth error is thrown', function() { - var model = { - getAccessToken: function() { - throw new Error('Unhandled exception'); - } - }; - var handler = new AuthenticateHandler({ model: model }); - var request = new Request({ body: {}, headers: { 'Authorization': 'Bearer foo' }, method: {}, query: {} }); - var response = new Response({ body: {}, headers: {} }); - - return handler.handle(request, response) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(ServerError); - e.message.should.equal('Unhandled exception'); - }); - }); - - it('should return an access token', function() { - var accessToken = { - user: {}, - accessTokenExpiresAt: new Date(new Date().getTime() + 10000) - }; - var model = { - getAccessToken: function() { - return accessToken; - }, - verifyScope: function() { - return true; - } - }; - var handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: true, model: model, scope: 'foo' }); - var request = new Request({ - body: {}, - headers: { 'Authorization': 'Bearer foo' }, - method: {}, - query: {} - }); - var response = new Response({ body: {}, headers: {} }); - - return handler.handle(request, response) - .then(function(data) { - data.should.equal(accessToken); - }) - .catch(should.fail); - }); - }); - - describe('getTokenFromRequest()', function() { - it('should throw an error if more than one authentication method is used', function() { - var handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); - var request = new Request({ - body: {}, - headers: { 'Authorization': 'Bearer foo' }, - method: {}, - query: { access_token: 'foo' } - }); - - try { - handler.getTokenFromRequest(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Invalid request: only one authentication method is allowed'); - } - }); - - it('should throw an error if `accessToken` is missing', function() { - var handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - try { - handler.getTokenFromRequest(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(UnauthorizedRequestError); - e.message.should.equal('Unauthorized request: no authentication given'); - } - }); - }); - - describe('getTokenFromRequestHeader()', function() { - it('should throw an error if the token is malformed', function() { - var handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); - var request = new Request({ - body: {}, - headers: { - 'Authorization': 'foobar' - }, - method: {}, - query: {} - }); - - try { - handler.getTokenFromRequestHeader(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Invalid request: malformed authorization header'); - } - }); - - it('should return the bearer token', function() { - var handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); - var request = new Request({ - body: {}, - headers: { - 'Authorization': 'Bearer foo' - }, - method: {}, - query: {} - }); - - var bearerToken = handler.getTokenFromRequestHeader(request); - - bearerToken.should.equal('foo'); - }); - }); - - describe('getTokenFromRequestQuery()', function() { - it('should throw an error if the query contains a token', function() { - var handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); - - try { - handler.getTokenFromRequestQuery(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Invalid request: do not send bearer tokens in query URLs'); - } - }); - - it('should return the bearer token if `allowBearerTokensInQueryString` is true', function() { - var handler = new AuthenticateHandler({ allowBearerTokensInQueryString: true, model: { getAccessToken: function() {} } }); - - handler.getTokenFromRequestQuery({ query: { access_token: 'foo' } }).should.equal('foo'); - }); - }); - - describe('getTokenFromRequestBody()', function() { - it('should throw an error if the method is `GET`', function() { - var handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); - var request = new Request({ - body: { access_token: 'foo' }, - headers: {}, - method: 'GET', - query: {} - }); - - try { - handler.getTokenFromRequestBody(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Invalid request: token may not be passed in the body when using the GET verb'); - } - }); - - it('should throw an error if the media type is not `application/x-www-form-urlencoded`', function() { - var handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); - var request = new Request({ - body: { access_token: 'foo' }, - headers: {}, - method: {}, - query: {} - }); - - try { - handler.getTokenFromRequestBody(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Invalid request: content must be application/x-www-form-urlencoded'); - } - }); - - it('should return the bearer token', function() { - var handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); - var request = new Request({ - body: { access_token: 'foo' }, - headers: { 'content-type': 'application/x-www-form-urlencoded', 'transfer-encoding': 'chunked' }, - method: {}, - query: {} - }); - - handler.getTokenFromRequestBody(request).should.equal('foo'); - }); - }); - - describe('getAccessToken()', function() { - it('should throw an error if `accessToken` is missing', function() { - var model = { - getAccessToken: function() {} - }; - var handler = new AuthenticateHandler({ model: model }); - - return handler.getAccessToken('foo') - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidTokenError); - e.message.should.equal('Invalid token: access token is invalid'); - }); - }); - - it('should throw an error if `accessToken.user` is missing', function() { - var model = { - getAccessToken: function() { - return {}; - } - }; - var handler = new AuthenticateHandler({ model: model }); - - return handler.getAccessToken('foo') - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(ServerError); - e.message.should.equal('Server error: `getAccessToken()` did not return a `user` object'); - }); - }); - - it('should return an access token', function() { - var accessToken = { user: {} }; - var model = { - getAccessToken: function() { - return accessToken; - } - }; - var handler = new AuthenticateHandler({ model: model }); - - return handler.getAccessToken('foo') - .then(function(data) { - data.should.equal(accessToken); - }) - .catch(should.fail); - }); - - it('should support promises', function() { - var model = { - getAccessToken: function() { - return Promise.resolve({ user: {} }); - } - }; - var handler = new AuthenticateHandler({ model: model }); - - handler.getAccessToken('foo').should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var model = { - getAccessToken: function() { - return { user: {} }; - } - }; - var handler = new AuthenticateHandler({ model: model }); - - handler.getAccessToken('foo').should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function() { - var model = { - getAccessToken: function(token, callback) { - callback(null, { user: {} }); - } - }; - var handler = new AuthenticateHandler({ model: model }); - - handler.getAccessToken('foo').should.be.an.instanceOf(Promise); - }); - }); - - describe('validateAccessToken()', function() { - it('should throw an error if `accessToken` is expired', function() { - var accessToken = { accessTokenExpiresAt: new Date(new Date() / 2) }; - var handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); - - try { - handler.validateAccessToken(accessToken); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidTokenError); - e.message.should.equal('Invalid token: access token has expired'); - } - }); - - it('should return an access token', function() { - var accessToken = { - user: {}, - accessTokenExpiresAt: new Date(new Date().getTime() + 10000) - }; - var handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); - - handler.validateAccessToken(accessToken).should.equal(accessToken); - }); - }); - - describe('verifyScope()', function() { - it('should throw an error if `scope` is insufficient', function() { - var model = { - getAccessToken: function() {}, - verifyScope: function() { - return false; - } - }; - var handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: true, model: model, scope: 'foo' }); - - return handler.verifyScope('foo') - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InsufficientScopeError); - e.message.should.equal('Insufficient scope: authorized scope is insufficient'); - }); - }); - - it('should support promises', function() { - var model = { - getAccessToken: function() {}, - verifyScope: function() { - return true; - } - }; - var handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: true, model: model, scope: 'foo' }); - - handler.verifyScope('foo').should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var model = { - getAccessToken: function() {}, - verifyScope: function() { - return true; - } - }; - var handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: true, model: model, scope: 'foo' }); - - handler.verifyScope('foo').should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function() { - var model = { - getAccessToken: function() {}, - verifyScope: function(token, scope, callback) { - callback(null, true); - } - }; - var handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: true, model: model, scope: 'foo' }); - - handler.verifyScope('foo').should.be.an.instanceOf(Promise); - }); - }); - - describe('updateResponse()', function() { - it('should not set the `X-Accepted-OAuth-Scopes` header if `scope` is not specified', function() { - var model = { - getAccessToken: function() {}, - verifyScope: function() {} - }; - var handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: false, model: model }); - var response = new Response({ body: {}, headers: {} }); - - handler.updateResponse(response, { scope: 'foo biz' }); - - response.headers.should.not.have.property('x-accepted-oauth-scopes'); - }); - - it('should set the `X-Accepted-OAuth-Scopes` header if `scope` is specified', function() { - var model = { - getAccessToken: function() {}, - verifyScope: function() {} - }; - var handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: false, model: model, scope: 'foo bar' }); - var response = new Response({ body: {}, headers: {} }); - - handler.updateResponse(response, { scope: 'foo biz' }); - - response.get('X-Accepted-OAuth-Scopes').should.equal('foo bar'); - }); - - it('should not set the `X-Authorized-OAuth-Scopes` header if `scope` is not specified', function() { - var model = { - getAccessToken: function() {}, - verifyScope: function() {} - }; - var handler = new AuthenticateHandler({ addAcceptedScopesHeader: false, addAuthorizedScopesHeader: true, model: model }); - var response = new Response({ body: {}, headers: {} }); - - handler.updateResponse(response, { scope: 'foo biz' }); - - response.headers.should.not.have.property('x-oauth-scopes'); - }); - - it('should set the `X-Authorized-OAuth-Scopes` header', function() { - var model = { - getAccessToken: function() {}, - verifyScope: function() {} - }; - var handler = new AuthenticateHandler({ addAcceptedScopesHeader: false, addAuthorizedScopesHeader: true, model: model, scope: 'foo bar' }); - var response = new Response({ body: {}, headers: {} }); - - handler.updateResponse(response, { scope: 'foo biz' }); - - response.get('X-OAuth-Scopes').should.equal('foo biz'); - }); - }); -}); diff --git a/test/integration/handlers/authorize-handler.spec.ts b/test/integration/handlers/authorize-handler.spec.ts new file mode 100755 index 000000000..b0002600d --- /dev/null +++ b/test/integration/handlers/authorize-handler.spec.ts @@ -0,0 +1,1570 @@ +import * as should from 'should'; +import * as url from 'url'; +import { AccessDeniedError } from '../../../lib/errors/access-denied-error'; +import { InvalidArgumentError } from '../../../lib/errors/invalid-argument-error'; +import { InvalidClientError } from '../../../lib/errors/invalid-client-error'; +import { InvalidRequestError } from '../../../lib/errors/invalid-request-error'; +import { InvalidScopeError } from '../../../lib/errors/invalid-scope-error'; +import { ServerError } from '../../../lib/errors/server-error'; +import { UnauthorizedClientError } from '../../../lib/errors/unauthorized-client-error'; +import { UnsupportedResponseTypeError } from '../../../lib/errors/unsupported-response-type-error'; +import { AuthenticateHandler } from '../../../lib/handlers/authenticate-handler'; +import { AuthorizeHandler } from '../../../lib/handlers/authorize-handler'; +import { Request } from '../../../lib/request'; +import { Response } from '../../../lib/response'; +import { CodeResponseType } from '../../../lib/response-types/code-response-type'; + +/** + * Test `AuthorizeHandler` integration. + */ + +describe('AuthorizeHandler integration', () => { + describe('constructor()', () => { + it('should throw an error if `options.authorizationCodeLifetime` is missing', () => { + try { + new AuthorizeHandler(); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Missing parameter: `authorizationCodeLifetime`', + ); + } + }); + + it('should throw an error if `options.model` is missing', () => { + try { + new AuthorizeHandler({ authorizationCodeLifetime: 120 }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `model`'); + } + }); + + it('should throw an error if the model does not implement `getClient()`', () => { + try { + new AuthorizeHandler({ authorizationCodeLifetime: 120, model: {} }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `getClient()`', + ); + } + }); + + it('should throw an error if the model does not implement `saveAuthorizationCode()`', () => { + try { + new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model: { getClient: () => {} }, + }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `saveAuthorizationCode()`', + ); + } + }); + + it('should throw an error if the model does not implement `getAccessToken()`', () => { + const model = { + getClient: () => {}, + saveAuthorizationCode: () => {}, + }; + + try { + new AuthorizeHandler({ authorizationCodeLifetime: 120, model }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `getAccessToken()`', + ); + } + }); + + it('should set the `authorizationCodeLifetime`', () => { + const model = { + getAccessToken: () => {}, + getClient: () => {}, + saveAuthorizationCode: () => {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + + handler.authorizationCodeLifetime.should.equal(120); + }); + + it('should set the `authenticateHandler`', () => { + const model = { + getAccessToken: () => {}, + getClient: () => {}, + saveAuthorizationCode: () => {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + + handler.authenticateHandler.should.be.an.instanceOf(AuthenticateHandler); + }); + + it('should set the `model`', () => { + const model = { + getAccessToken: () => {}, + getClient: () => {}, + saveAuthorizationCode: () => {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + + handler.model.should.equal(model); + }); + }); + + describe('handle()', () => { + it('should throw an error if `request` is missing', async () => { + const model = { + getAccessToken: () => {}, + getClient: () => {}, + saveAuthorizationCode: () => {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + + try { + await handler.handle(undefined, undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: `request` must be an instance of Request', + ); + } + }); + + it('should throw an error if `response` is missing', async () => { + const model = { + getAccessToken: () => {}, + getClient: () => {}, + saveAuthorizationCode: () => {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: {}, + }); + + try { + await handler.handle(request, undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: `response` must be an instance of Response', + ); + } + }); + + it('should throw an error if `allowed` is `false`', () => { + const model = { + getAccessToken: () => {}, + getClient: () => {}, + saveAuthorizationCode: () => {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: { allowed: 'false' }, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => should.fail('should.fail', '')) + .catch(e => { + e.should.be.an.instanceOf(AccessDeniedError); + e.message.should.equal( + 'Access denied: user denied access to application', + ); + }); + }); + + it('should redirect to an error response if a non-oauth error is thrown', () => { + const model = { + getAccessToken: () => { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + getClient: () => { + return { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + }, + saveAuthorizationCode: () => { + throw new Error('Unhandled exception'); + }, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { + client_id: 12345, + response_type: 'code', + }, + headers: { + Authorization: 'Bearer foo', + }, + method: {}, + query: { + state: 'foobar', + }, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => should.fail('should.fail', '')) + .catch(() => { + response + .get('location') + .should.equal( + 'http://example.com/cb?error=server_error&error_description=Unhandled%20exception&state=foobar', + ); + }); + }); + + it('should redirect to an error response if an oauth error is thrown', () => { + const model = { + getAccessToken: () => { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + getClient: () => { + return { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + }, + saveAuthorizationCode: () => { + throw new AccessDeniedError('Cannot request this auth code'); + }, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { + client_id: 12345, + response_type: 'code', + }, + headers: { + Authorization: 'Bearer foo', + }, + method: {}, + query: { + state: 'foobar', + }, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(() => { + response.get('location').should.equal( + // tslint:disable-next-line:max-line-length + 'http://example.com/cb?error=access_denied&error_description=Cannot%20request%20this%20auth%20code&state=foobar', + ); + }); + }); + + it('should redirect to a successful response with `code` and `state` if successful', () => { + const client = { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + const model = { + getAccessToken: () => { + return { + client, + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + getClient: () => { + return client; + }, + saveAuthorizationCode: () => { + return { authorizationCode: 12345, client }; + }, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { + client_id: 12345, + response_type: 'code', + }, + headers: { + Authorization: 'Bearer foo', + }, + method: {}, + query: { + state: 'foobar', + }, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => { + response + .get('location') + .should.equal('http://example.com/cb?code=12345&state=foobar'); + }) + .catch(() => should.fail('should.fail', '')); + }); + + it('should redirect to an error response if `scope` is invalid', () => { + const model = { + getAccessToken: () => { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + getClient: () => { + return { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + }, + saveAuthorizationCode: () => { + return {}; + }, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { + client_id: 12345, + response_type: 'code', + }, + headers: { + Authorization: 'Bearer foo', + }, + method: {}, + query: { + scope: [], + state: 'foobar', + }, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => should.fail('should.fail', '')) + .catch(() => { + response + .get('location') + .should.equal( + 'http://example.com/cb?error=invalid_scope&error_description=Invalid%20parameter%3A%20%60scope%60', + ); + }); + }); + + it('should redirect to an error response if `state` is missing', () => { + const model = { + getAccessToken: () => { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + getClient: () => { + return { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + }, + saveAuthorizationCode: () => { + throw new AccessDeniedError('Cannot request this auth code'); + }, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { + client_id: 12345, + response_type: 'code', + }, + headers: { + Authorization: 'Bearer foo', + }, + method: {}, + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => should.fail('should.fail', '')) + .catch(() => { + response + .get('location') + .should.equal( + 'http://example.com/cb?error=invalid_request&error_description=Missing%20parameter%3A%20%60state%60', + ); + }); + }); + + it('should redirect to an error response if `response_type` is invalid', () => { + const model = { + getAccessToken: () => { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + getClient: () => { + return { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + }, + saveAuthorizationCode: () => { + return { authorizationCode: 12345, client: {} }; + }, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { + client_id: 12345, + response_type: 'test', + }, + headers: { + Authorization: 'Bearer foo', + }, + method: {}, + query: { + state: 'foobar', + }, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => should.fail('should.fail', '')) + .catch(() => { + response.get('location').should.equal( + // tslint:disable-next-line:max-line-length + 'http://example.com/cb?error=unsupported_response_type&error_description=Unsupported%20response%20type%3A%20%60response_type%60%20is%20not%20supported&state=foobar', + ); + }); + }); + + it('should fail on invalid `response_type` before calling model.saveAuthorizationCode()', () => { + const model = { + getAccessToken: () => { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + getClient: () => { + return { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + }, + saveAuthorizationCode: () => { + throw new Error('must not be reached'); + }, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { + client_id: 12345, + response_type: 'test', + }, + headers: { + Authorization: 'Bearer foo', + }, + method: {}, + query: { + state: 'foobar', + }, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => should.fail('should.fail', '')) + .catch(() => { + response.get('location').should.equal( + // tslint:disable-next-line:max-line-length + 'http://example.com/cb?error=unsupported_response_type&error_description=Unsupported%20response%20type%3A%20%60response_type%60%20is%20not%20supported&state=foobar', + ); + }); + }); + + it('should return the `code` if successful', () => { + const client = { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + const model = { + getAccessToken: () => { + return { + client, + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + getClient() { + return client; + }, + saveAuthorizationCode() { + return { authorizationCode: 12345, client }; + }, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { + client_id: 12345, + response_type: 'code', + }, + headers: { + Authorization: 'Bearer foo', + }, + method: {}, + query: { + state: 'foobar', + }, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(data => { + data.should.eql({ + authorizationCode: 12345, + client, + }); + }) + .catch(() => should.fail('should.fail', '')); + }); + }); + + describe('generateAuthorizationCode()', () => { + it('should return an auth code', async () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + try { + const data: any = await handler.generateAuthorizationCode( + undefined, + undefined, + undefined, + ); + data.should.be.a.sha1(); + } catch (error) { + should.fail('should.fail', ''); + } + }); + + it('should support promises', async () => { + const model = { + generateAuthorizationCode() { + return Promise.resolve({}); + }, + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + try { + await handler + .generateAuthorizationCode(undefined, undefined, undefined) + .should.be.an.instanceOf(Promise); + } catch (error) { + should.fail('should.fail', ''); + } + }); + + /* it('should support non-promises', () => { + const model = { + generateAuthorizationCode() { + return {}; + }, + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + + handler + .generateAuthorizationCode(undefined, undefined, undefined) + .should.be.an.instanceOf(Promise); + }); */ + }); + + describe('getAuthorizationCodeLifetime()', () => { + it('should return a date', () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + + handler.getAuthorizationCodeLifetime().should.be.an.instanceOf(Date); + }); + }); + + describe('getClient()', () => { + it('should throw an error if `client_id` is missing', async () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { response_type: 'code' }, + headers: {}, + method: {}, + query: {}, + }); + + try { + await handler.getClient(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Missing parameter: `client_id`'); + } + }); + + it('should throw an error if `client_id` is invalid', async () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { client_id: 'øå€£‰', response_type: 'code' }, + headers: {}, + method: {}, + query: {}, + }); + + try { + await handler.getClient(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Invalid parameter: `client_id`'); + } + }); + + it('should throw an error if `client.redirectUri` is invalid', async () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { + client_id: 12345, + response_type: 'code', + redirect_uri: 'foobar', + }, + headers: {}, + method: {}, + query: {}, + }); + + try { + await handler.getClient(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal( + 'Invalid request: `redirect_uri` is not a valid URI', + ); + } + }); + + it('should throw an error if `client` is missing', () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { client_id: 12345, response_type: 'code' }, + headers: {}, + method: {}, + query: {}, + }); + + return handler + .getClient(request) + .then(() => should.fail('should.fail', '')) + .catch(e => { + e.should.be.an.instanceOf(InvalidClientError); + e.message.should.equal( + 'Invalid client: client credentials are invalid', + ); + }); + }); + + it('should throw an error if `client.grants` is missing', () => { + const model = { + getAccessToken() {}, + getClient() { + return {}; + }, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { client_id: 12345, response_type: 'code' }, + headers: {}, + method: {}, + query: {}, + }); + + return handler + .getClient(request) + .then(() => should.fail('should.fail', '')) + .catch(e => { + e.should.be.an.instanceOf(InvalidClientError); + e.message.should.equal('Invalid client: missing client `grants`'); + }); + }); + + it('should throw an error if `client` is unauthorized', () => { + const model = { + getAccessToken() {}, + getClient() { + return { grants: [] }; + }, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { client_id: 12345, response_type: 'code' }, + headers: {}, + method: {}, + query: {}, + }); + + return handler + .getClient(request) + .then(() => should.fail('should.fail', '')) + .catch(e => { + e.should.be.an.instanceOf(UnauthorizedClientError); + e.message.should.equal( + 'Unauthorized client: `grant_type` is invalid', + ); + }); + }); + + it('should throw an error if `client.redirectUri` is missing', () => { + const model = { + getAccessToken() {}, + getClient() { + return { grants: ['authorization_code'] }; + }, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { client_id: 12345, response_type: 'code' }, + headers: {}, + method: {}, + query: {}, + }); + + return handler + .getClient(request) + .then(() => should.fail('should.fail', '')) + .catch(e => { + e.should.be.an.instanceOf(InvalidClientError); + e.message.should.equal( + 'Invalid client: missing client `redirectUri`', + ); + }); + }); + + it('should throw an error if `client.redirectUri` is not equal to `redirectUri`', () => { + const model = { + getAccessToken() {}, + getClient() { + return { + grants: ['authorization_code'], + redirectUris: ['https://example.com'], + }; + }, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { + client_id: 12345, + response_type: 'code', + redirect_uri: 'https://foobar.com', + }, + headers: {}, + method: {}, + query: {}, + }); + + return handler + .getClient(request) + .then(() => should.fail('should.fail', '')) + .catch(e => { + e.should.be.an.instanceOf(InvalidClientError); + e.message.should.equal( + 'Invalid client: `redirect_uri` does not match client value', + ); + }); + }); + + it('should support promises', async () => { + const model = { + getAccessToken() {}, + getClient() { + return Promise.resolve({ + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }); + }, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { client_id: 12345 }, + headers: {}, + method: {}, + query: {}, + }); + try { + await handler.getClient(request).should.be.an.instanceOf(Promise); + } catch (error) { + should.fail('should.fail', ''); + } + }); + + /* it('should support non-promises', async () => { + const model = { + getAccessToken() {}, + getClient() { + return { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + }, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { client_id: 12345 }, + headers: {}, + method: {}, + query: {}, + }); + + await handler.getClient(request).should.be.an.instanceOf(Promise); + }); */ + + /* it('should support callbacks', () => { + const model = { + getAccessToken() {}, + getClient(clientId, clientSecret, callback) { + should.equal(clientSecret, null); + callback(null, { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }); + }, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { client_id: 12345 }, + headers: {}, + method: {}, + query: {}, + }); + + handler.getClient(request).should.be.an.instanceOf(Promise); + }); */ + + describe('with `client_id` in the request query', () => { + it('should return a client', () => { + const client = { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + const model = { + getAccessToken() {}, + getClient() { + return client; + }, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { response_type: 'code' }, + headers: {}, + method: {}, + query: { client_id: 12345 }, + }); + + return handler + .getClient(request) + .then(data => { + data.should.equal(client); + }) + .catch(() => should.fail('should.fail', '')); + }); + }); + }); + + describe('getScope()', () => { + it('should throw an error if `scope` is invalid', () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { scope: 'øå€£‰' }, + headers: {}, + method: {}, + query: {}, + }); + + try { + handler.getScope(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidScopeError); + e.message.should.equal('Invalid parameter: `scope`'); + } + }); + + describe('with `scope` in the request body', () => { + it('should return the scope', () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { scope: 'foo' }, + headers: {}, + method: {}, + query: {}, + }); + + handler.getScope(request).should.equal('foo'); + }); + }); + + describe('with `scope` in the request query', () => { + it('should return the scope', () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: { scope: 'foo' }, + }); + + handler.getScope(request).should.equal('foo'); + }); + }); + }); + + describe('getState()', () => { + it('should throw an error if `allowEmptyState` is false and `state` is missing', () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + allowEmptyState: false, + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: {}, + }); + + try { + handler.getState(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Missing parameter: `state`'); + } + }); + + it('should throw an error if `state` is invalid', () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: { state: 'øå€£‰' }, + }); + + try { + handler.getState(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Invalid parameter: `state`'); + } + }); + + describe('with `state` in the request body', () => { + it('should return the state', () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { state: 'foobar' }, + headers: {}, + method: {}, + query: {}, + }); + + handler.getState(request).should.equal('foobar'); + }); + }); + + describe('with `state` in the request query', () => { + it('should return the state', () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: { state: 'foobar' }, + }); + + handler.getState(request).should.equal('foobar'); + }); + }); + }); + + describe('getUser()', () => { + it('should throw an error if `user` is missing', () => { + const authenticateHandler = { handle() {} }; + const model = { + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authenticateHandler, + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: {}, + }); + const response = new Response(); + + return handler + .getUser(request, response) + .then(() => should.fail('should.fail', '')) + .catch(e => { + e.should.be.an.instanceOf(ServerError); + e.message.should.equal( + 'Server error: `handle()` did not return a `user` object', + ); + }); + }); + + it('should return a user', () => { + const user = {}; + const model = { + getAccessToken() { + return { + user, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: { Authorization: 'Bearer foo' }, + method: {}, + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .getUser(request, response) + .then(data => { + data.should.equal(user); + }) + .catch(() => should.fail('should.fail', '')); + }); + }); + + describe('saveAuthorizationCode()', () => { + it('should return an auth code', () => { + const authorizationCode = {}; + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() { + return authorizationCode; + }, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + + return handler + .saveAuthorizationCode( + 'foo', + 'bar' as any, + 'biz', + 'baz' as any, + undefined, + undefined, + ) + .then(data => { + data.should.equal(authorizationCode); + }) + .catch(() => should.fail('should.fail', '')); + }); + + it('should support promises when calling `model.saveAuthorizationCode()`', () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() { + return Promise.resolve({}); + }, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + + handler + .saveAuthorizationCode( + 'foo', + 'bar' as any, + 'biz', + 'baz' as any, + undefined, + undefined, + ) + .should.be.an.instanceOf(Promise); + }); + + /* it('should support non-promises when calling `model.saveAuthorizationCode()`', () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() { + return {}; + }, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + + handler + .saveAuthorizationCode('foo', 'bar', 'biz', 'baz', undefined, undefined) + .should.be.an.instanceOf(Promise); + }); */ + + /* it('should support callbacks when calling `model.saveAuthorizationCode()`', () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode(code, client, user, callback) { + return callback(null, true); + }, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + + handler + .saveAuthorizationCode('foo', 'bar', 'biz', 'baz') + .should.be.an.instanceOf(Promise); + }); */ + }); + + describe('getResponseType()', () => { + it('should throw an error if `response_type` is missing', () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: {}, + }); + + try { + handler.getResponseType(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Missing parameter: `response_type`'); + } + }); + + it('should throw an error if `response_type` is not `code`', () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { response_type: 'foobar' }, + headers: {}, + method: {}, + query: {}, + }); + + try { + handler.getResponseType(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(UnsupportedResponseTypeError); + e.message.should.equal( + 'Unsupported response type: `response_type` is not supported', + ); + } + }); + + describe('with `response_type` in the request body', () => { + it('should return a response type', () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { response_type: 'code' }, + headers: {}, + method: {}, + query: {}, + }); + const ResponseType = handler.getResponseType(request); + + ResponseType.should.equal(CodeResponseType); + }); + }); + + describe('with `response_type` in the request query', () => { + it('should return a response type', () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: { response_type: 'code' }, + }); + const ResponseType = handler.getResponseType(request); + + ResponseType.should.equal(CodeResponseType); + }); + }); + }); + + describe('buildSuccessRedirectUri()', () => { + it('should return a redirect uri', () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const responseType = new CodeResponseType(12345); + const redirectUri = handler.buildSuccessRedirectUri( + 'http://example.com/cb', + responseType, + ); + + url.format(redirectUri).should.equal('http://example.com/cb?code=12345'); + }); + }); + + describe('buildErrorRedirectUri()', () => { + it('should set `error_description` if available', () => { + const error = new InvalidClientError('foo bar'); + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const redirectUri = handler.buildErrorRedirectUri( + 'http://example.com/cb', + error, + ); + + url + .format(redirectUri) + .should.equal( + 'http://example.com/cb?error=invalid_client&error_description=foo%20bar', + ); + }); + + it('should return a redirect uri', () => { + const error = new InvalidClientError(); + const model = { + async getAccessToken() {}, + async getClient() {}, + async saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const redirectUri = handler.buildErrorRedirectUri( + 'http://example.com/cb', + error, + ); + + url + .format(redirectUri) + .should.equal( + 'http://example.com/cb?error=invalid_client&error_description=Bad%20Request', + ); + }); + }); + + describe('updateResponse()', () => { + it('should set the `location` header', () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const response = new Response({ body: {}, headers: {} }); + const uri = url.parse('http://example.com/cb', true); + + handler.updateResponse(response, uri, 'foobar'); + + response + .get('location') + .should.equal('http://example.com/cb?state=foobar'); + }); + }); +}); diff --git a/test/integration/handlers/authorize-handler_test.js b/test/integration/handlers/authorize-handler_test.js deleted file mode 100644 index 0d1aa333b..000000000 --- a/test/integration/handlers/authorize-handler_test.js +++ /dev/null @@ -1,1126 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var AccessDeniedError = require('../../../lib/errors/access-denied-error'); -var AuthenticateHandler = require('../../../lib/handlers/authenticate-handler'); -var AuthorizeHandler = require('../../../lib/handlers/authorize-handler'); -var CodeResponseType = require('../../../lib/response-types/code-response-type'); -var InvalidArgumentError = require('../../../lib/errors/invalid-argument-error'); -var InvalidClientError = require('../../../lib/errors/invalid-client-error'); -var InvalidRequestError = require('../../../lib/errors/invalid-request-error'); -var InvalidScopeError = require('../../../lib/errors/invalid-scope-error'); -var UnsupportedResponseTypeError = require('../../../lib/errors/unsupported-response-type-error'); -var Promise = require('bluebird'); -var Request = require('../../../lib/request'); -var Response = require('../../../lib/response'); -var ServerError = require('../../../lib/errors/server-error'); -var UnauthorizedClientError = require('../../../lib/errors/unauthorized-client-error'); -var should = require('should'); -var url = require('url'); - -/** - * Test `AuthorizeHandler` integration. - */ - -describe('AuthorizeHandler integration', function() { - describe('constructor()', function() { - it('should throw an error if `options.authorizationCodeLifetime` is missing', function() { - try { - new AuthorizeHandler(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `authorizationCodeLifetime`'); - } - }); - - it('should throw an error if `options.model` is missing', function() { - try { - new AuthorizeHandler({ authorizationCodeLifetime: 120 }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `model`'); - } - }); - - it('should throw an error if the model does not implement `getClient()`', function() { - try { - new AuthorizeHandler({ authorizationCodeLifetime: 120, model: {} }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: model does not implement `getClient()`'); - } - }); - - it('should throw an error if the model does not implement `saveAuthorizationCode()`', function() { - try { - new AuthorizeHandler({ authorizationCodeLifetime: 120, model: { getClient: function() {} } }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: model does not implement `saveAuthorizationCode()`'); - } - }); - - it('should throw an error if the model does not implement `getAccessToken()`', function() { - var model = { - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - - try { - new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: model does not implement `getAccessToken()`'); - } - }); - - it('should set the `authorizationCodeLifetime`', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - - handler.authorizationCodeLifetime.should.equal(120); - }); - - it('should set the `authenticateHandler`', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - - handler.authenticateHandler.should.be.an.instanceOf(AuthenticateHandler); - }); - - it('should set the `model`', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - - handler.model.should.equal(model); - }); - }); - - describe('handle()', function() { - it('should throw an error if `request` is missing', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - - try { - handler.handle(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: `request` must be an instance of Request'); - } - }); - - it('should throw an error if `response` is missing', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - try { - handler.handle(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: `response` must be an instance of Response'); - } - }); - - it('should throw an error if `allowed` is `false`', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: { allowed: 'false' } }); - var response = new Response({ body: {}, headers: {} }); - - return handler.handle(request, response) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(AccessDeniedError); - e.message.should.equal('Access denied: user denied access to application'); - }); - }); - - it('should redirect to an error response if a non-oauth error is thrown', function() { - var model = { - getAccessToken: function() { - return { - user: {}, - accessTokenExpiresAt: new Date(new Date().getTime() + 10000) - }; - }, - getClient: function() { - return { grants: ['authorization_code'], redirectUris: ['http://example.com/cb'] }; - }, - saveAuthorizationCode: function() { - throw new Error('Unhandled exception'); - } - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ - body: { - client_id: 12345, - response_type: 'code' - }, - headers: { - 'Authorization': 'Bearer foo' - }, - method: {}, - query: { - state: 'foobar' - } - }); - var response = new Response({ body: {}, headers: {} }); - - return handler.handle(request, response) - .then(should.fail) - .catch(function() { - response.get('location').should.equal('http://example.com/cb?error=server_error&error_description=Unhandled%20exception&state=foobar'); - }); - }); - - it('should redirect to an error response if an oauth error is thrown', function() { - var model = { - getAccessToken: function() { - return { - user: {}, - accessTokenExpiresAt: new Date(new Date().getTime() + 10000) - }; - }, - getClient: function() { - return { grants: ['authorization_code'], redirectUris: ['http://example.com/cb'] }; - }, - saveAuthorizationCode: function() { - throw new AccessDeniedError('Cannot request this auth code'); - } - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ - body: { - client_id: 12345, - response_type: 'code' - }, - headers: { - 'Authorization': 'Bearer foo' - }, - method: {}, - query: { - state: 'foobar' - } - }); - var response = new Response({ body: {}, headers: {} }); - - return handler.handle(request, response) - .then(should.fail) - .catch(function() { - response.get('location').should.equal('http://example.com/cb?error=access_denied&error_description=Cannot%20request%20this%20auth%20code&state=foobar'); - }); - }); - - it('should redirect to a successful response with `code` and `state` if successful', function() { - var client = { grants: ['authorization_code'], redirectUris: ['http://example.com/cb'] }; - var model = { - getAccessToken: function() { - return { - client: client, - user: {}, - accessTokenExpiresAt: new Date(new Date().getTime() + 10000) - }; - }, - getClient: function() { - return client; - }, - saveAuthorizationCode: function() { - return { authorizationCode: 12345, client: client }; - } - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ - body: { - client_id: 12345, - response_type: 'code' - }, - headers: { - 'Authorization': 'Bearer foo' - }, - method: {}, - query: { - state: 'foobar' - } - }); - var response = new Response({ body: {}, headers: {} }); - - return handler.handle(request, response) - .then(function() { - response.get('location').should.equal('http://example.com/cb?code=12345&state=foobar'); - }) - .catch(should.fail); - }); - - it('should redirect to an error response if `scope` is invalid', function() { - var model = { - getAccessToken: function() { - return { - user: {}, - accessTokenExpiresAt: new Date(new Date().getTime() + 10000) - }; - }, - getClient: function() { - return { grants: ['authorization_code'], redirectUris: ['http://example.com/cb'] }; - }, - saveAuthorizationCode: function() { - return {}; - } - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ - body: { - client_id: 12345, - response_type: 'code' - }, - headers: { - 'Authorization': 'Bearer foo' - }, - method: {}, - query: { - scope: [], - state: 'foobar' - } - }); - var response = new Response({ body: {}, headers: {} }); - - return handler.handle(request, response) - .then(should.fail) - .catch(function() { - response.get('location').should.equal('http://example.com/cb?error=invalid_scope&error_description=Invalid%20parameter%3A%20%60scope%60'); - }); - }); - - it('should redirect to an error response if `state` is missing', function() { - var model = { - getAccessToken: function() { - return { - user: {}, - accessTokenExpiresAt: new Date(new Date().getTime() + 10000) - }; - }, - getClient: function() { - return { grants: ['authorization_code'], redirectUris: ['http://example.com/cb'] }; - }, - saveAuthorizationCode: function() { - throw new AccessDeniedError('Cannot request this auth code'); - } - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ - body: { - client_id: 12345, - response_type: 'code' - }, - headers: { - 'Authorization': 'Bearer foo' - }, - method: {}, - query: {} - }); - var response = new Response({ body: {}, headers: {} }); - - return handler.handle(request, response) - .then(should.fail) - .catch(function() { - response.get('location').should.equal('http://example.com/cb?error=invalid_request&error_description=Missing%20parameter%3A%20%60state%60'); - }); - }); - - it('should redirect to an error response if `response_type` is invalid', function() { - var model = { - getAccessToken: function() { - return { - user: {}, - accessTokenExpiresAt: new Date(new Date().getTime() + 10000) - }; - }, - getClient: function() { - return { grants: ['authorization_code'], redirectUris: ['http://example.com/cb'] }; - }, - saveAuthorizationCode: function() { - return { authorizationCode: 12345, client: {} }; - } - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ - body: { - client_id: 12345, - response_type: 'test' - }, - headers: { - 'Authorization': 'Bearer foo' - }, - method: {}, - query: { - state: 'foobar' - } - }); - var response = new Response({ body: {}, headers: {} }); - - return handler.handle(request, response) - .then(should.fail) - .catch(function() { - response.get('location').should.equal('http://example.com/cb?error=unsupported_response_type&error_description=Unsupported%20response%20type%3A%20%60response_type%60%20is%20not%20supported&state=foobar'); - }); - }); - - it('should fail on invalid `response_type` before calling model.saveAuthorizationCode()', function() { - var model = { - getAccessToken: function() { - return { - user: {}, - accessTokenExpiresAt: new Date(new Date().getTime() + 10000) - }; - }, - getClient: function() { - return { grants: ['authorization_code'], redirectUris: ['http://example.com/cb'] }; - }, - saveAuthorizationCode: function() { - throw new Error('must not be reached'); - } - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ - body: { - client_id: 12345, - response_type: 'test' - }, - headers: { - 'Authorization': 'Bearer foo' - }, - method: {}, - query: { - state: 'foobar' - } - }); - var response = new Response({ body: {}, headers: {} }); - - return handler.handle(request, response) - .then(should.fail) - .catch(function() { - response.get('location').should.equal('http://example.com/cb?error=unsupported_response_type&error_description=Unsupported%20response%20type%3A%20%60response_type%60%20is%20not%20supported&state=foobar'); - }); - }); - - it('should return the `code` if successful', function() { - var client = { grants: ['authorization_code'], redirectUris: ['http://example.com/cb'] }; - var model = { - getAccessToken: function() { - return { - client: client, - user: {}, - accessTokenExpiresAt: new Date(new Date().getTime() + 10000) - }; - }, - getClient: function() { - return client; - }, - saveAuthorizationCode: function() { - return { authorizationCode: 12345, client: client }; - } - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ - body: { - client_id: 12345, - response_type: 'code' - }, - headers: { - 'Authorization': 'Bearer foo' - }, - method: {}, - query: { - state: 'foobar' - } - }); - var response = new Response({ body: {}, headers: {} }); - - return handler.handle(request, response) - .then(function(data) { - data.should.eql({ - authorizationCode: 12345, - client: client - }); - }) - .catch(should.fail); - }); - }); - - describe('generateAuthorizationCode()', function() { - it('should return an auth code', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - - return handler.generateAuthorizationCode() - .then(function(data) { - data.should.be.a.sha1; - }) - .catch(should.fail); - }); - - it('should support promises', function() { - var model = { - generateAuthorizationCode: function() { - return Promise.resolve({}); - }, - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - - handler.generateAuthorizationCode().should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var model = { - generateAuthorizationCode: function() { - return {}; - }, - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - - handler.generateAuthorizationCode().should.be.an.instanceOf(Promise); - }); - }); - - describe('getAuthorizationCodeLifetime()', function() { - it('should return a date', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - - handler.getAuthorizationCodeLifetime().should.be.an.instanceOf(Date); - }); - }); - - describe('getClient()', function() { - it('should throw an error if `client_id` is missing', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ body: { response_type: 'code' }, headers: {}, method: {}, query: {} }); - - try { - handler.getClient(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Missing parameter: `client_id`'); - } - }); - - it('should throw an error if `client_id` is invalid', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ body: { client_id: 'øå€£‰', response_type: 'code' }, headers: {}, method: {}, query: {} }); - - try { - handler.getClient(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Invalid parameter: `client_id`'); - } - }); - - it('should throw an error if `client.redirectUri` is invalid', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ body: { client_id: 12345, response_type: 'code', redirect_uri: 'foobar' }, headers: {}, method: {}, query: {} }); - - try { - handler.getClient(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Invalid request: `redirect_uri` is not a valid URI'); - } - }); - - it('should throw an error if `client` is missing', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ body: { client_id: 12345, response_type: 'code' }, headers: {}, method: {}, query: {} }); - - return handler.getClient(request) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidClientError); - e.message.should.equal('Invalid client: client credentials are invalid'); - }); - }); - - it('should throw an error if `client.grants` is missing', function() { - var model = { - getAccessToken: function() {}, - getClient: function() { - return {}; - }, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ body: { client_id: 12345, response_type: 'code' }, headers: {}, method: {}, query: {} }); - - return handler.getClient(request) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidClientError); - e.message.should.equal('Invalid client: missing client `grants`'); - }); - }); - - it('should throw an error if `client` is unauthorized', function() { - var model = { - getAccessToken: function() {}, - getClient: function() { - return { grants: [] }; - }, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ body: { client_id: 12345, response_type: 'code' }, headers: {}, method: {}, query: {} }); - - return handler.getClient(request) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(UnauthorizedClientError); - e.message.should.equal('Unauthorized client: `grant_type` is invalid'); - }); - }); - - it('should throw an error if `client.redirectUri` is missing', function() { - var model = { - getAccessToken: function() {}, - getClient: function() { return { grants: ['authorization_code'] }; }, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ body: { client_id: 12345, response_type: 'code' }, headers: {}, method: {}, query: {} }); - - return handler.getClient(request) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidClientError); - e.message.should.equal('Invalid client: missing client `redirectUri`'); - }); - }); - - it('should throw an error if `client.redirectUri` is not equal to `redirectUri`', function() { - var model = { - getAccessToken: function() {}, - getClient: function() { - return { grants: ['authorization_code'], redirectUris: ['https://example.com'] }; - }, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ body: { client_id: 12345, response_type: 'code', redirect_uri: 'https://foobar.com' }, headers: {}, method: {}, query: {} }); - - return handler.getClient(request) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidClientError); - e.message.should.equal('Invalid client: `redirect_uri` does not match client value'); - }); - }); - - it('should support promises', function() { - var model = { - getAccessToken: function() {}, - getClient: function() { - return Promise.resolve({ grants: ['authorization_code'], redirectUris: ['http://example.com/cb'] }); - }, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ - body: { client_id: 12345 }, - headers: {}, - method: {}, - query: {} - }); - - handler.getClient(request).should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var model = { - getAccessToken: function() {}, - getClient: function() { - return { grants: ['authorization_code'], redirectUris: ['http://example.com/cb'] }; - }, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ - body: { client_id: 12345 }, - headers: {}, - method: {}, - query: {} - }); - - handler.getClient(request).should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function() { - var model = { - getAccessToken: function() {}, - getClient: function(clientId, clientSecret, callback) { - should.equal(clientSecret, null); - callback(null, { grants: ['authorization_code'], redirectUris: ['http://example.com/cb'] }); - }, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ - body: { client_id: 12345 }, - headers: {}, - method: {}, - query: {} - }); - - handler.getClient(request).should.be.an.instanceOf(Promise); - }); - - describe('with `client_id` in the request query', function() { - it('should return a client', function() { - var client = { grants: ['authorization_code'], redirectUris: ['http://example.com/cb'] }; - var model = { - getAccessToken: function() {}, - getClient: function() { - return client; - }, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ body: { response_type: 'code' }, headers: {}, method: {}, query: { client_id: 12345 } }); - - return handler.getClient(request) - .then(function(data) { - data.should.equal(client); - }) - .catch(should.fail); - }); - }); - }); - - describe('getScope()', function() { - it('should throw an error if `scope` is invalid', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ body: { scope: 'øå€£‰' }, headers: {}, method: {}, query: {} }); - - try { - handler.getScope(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidScopeError); - e.message.should.equal('Invalid parameter: `scope`'); - } - }); - - describe('with `scope` in the request body', function() { - it('should return the scope', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ body: { scope: 'foo' }, headers: {}, method: {}, query: {} }); - - handler.getScope(request).should.equal('foo'); - }); - }); - - describe('with `scope` in the request query', function() { - it('should return the scope', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: { scope: 'foo' } }); - - handler.getScope(request).should.equal('foo'); - }); - }); - }); - - describe('getState()', function() { - it('should throw an error if `allowEmptyState` is false and `state` is missing', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ allowEmptyState: false, authorizationCodeLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - try { - handler.getState(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Missing parameter: `state`'); - } - }); - - it('should throw an error if `state` is invalid', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: { state: 'øå€£‰' } }); - - try { - handler.getState(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Invalid parameter: `state`'); - } - }); - - describe('with `state` in the request body', function() { - it('should return the state', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ body: { state: 'foobar' }, headers: {}, method: {}, query: {} }); - - handler.getState(request).should.equal('foobar'); - }); - }); - - describe('with `state` in the request query', function() { - it('should return the state', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: { state: 'foobar' } }); - - handler.getState(request).should.equal('foobar'); - }); - }); - }); - - describe('getUser()', function() { - it('should throw an error if `user` is missing', function() { - var authenticateHandler = { handle: function() {} }; - var model = { - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authenticateHandler: authenticateHandler, authorizationCodeLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - var response = new Response(); - - return handler.getUser(request, response) - .then(should.fail) - .catch(function (e) { - e.should.be.an.instanceOf(ServerError); - e.message.should.equal('Server error: `handle()` did not return a `user` object'); - }); - }); - - it('should return a user', function() { - var user = {}; - var model = { - getAccessToken: function() { - return { - user: user, - accessTokenExpiresAt: new Date(new Date().getTime() + 10000) - }; - }, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: { 'Authorization': 'Bearer foo' }, method: {}, query: {} }); - var response = new Response({ body: {}, headers: {} }); - - return handler.getUser(request, response) - .then(function(data) { - data.should.equal(user); - }) - .catch(should.fail); - }); - }); - - describe('saveAuthorizationCode()', function() { - it('should return an auth code', function() { - var authorizationCode = {}; - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() { - return authorizationCode; - } - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - - return handler.saveAuthorizationCode('foo', 'bar', 'biz', 'baz') - .then(function(data) { - data.should.equal(authorizationCode); - }) - .catch(should.fail); - }); - - it('should support promises when calling `model.saveAuthorizationCode()`', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() { - return Promise.resolve({}); - } - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - - handler.saveAuthorizationCode('foo', 'bar', 'biz', 'baz').should.be.an.instanceOf(Promise); - }); - - it('should support non-promises when calling `model.saveAuthorizationCode()`', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() { - return {}; - } - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - - handler.saveAuthorizationCode('foo', 'bar', 'biz', 'baz').should.be.an.instanceOf(Promise); - }); - - it('should support callbacks when calling `model.saveAuthorizationCode()`', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function(code, client, user, callback) { - return callback(null, true); - } - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - - handler.saveAuthorizationCode('foo', 'bar', 'biz', 'baz').should.be.an.instanceOf(Promise); - }); - }); - - describe('getResponseType()', function() { - it('should throw an error if `response_type` is missing', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - try { - handler.getResponseType(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Missing parameter: `response_type`'); - } - }); - - it('should throw an error if `response_type` is not `code`', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ body: { response_type: 'foobar' }, headers: {}, method: {}, query: {} }); - - try { - handler.getResponseType(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(UnsupportedResponseTypeError); - e.message.should.equal('Unsupported response type: `response_type` is not supported'); - } - }); - - describe('with `response_type` in the request body', function() { - it('should return a response type', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ body: { response_type: 'code' }, headers: {}, method: {}, query: {} }); - var ResponseType = handler.getResponseType(request); - - ResponseType.should.equal(CodeResponseType); - }); - }); - - describe('with `response_type` in the request query', function() { - it('should return a response type', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: { response_type: 'code' } }); - var ResponseType = handler.getResponseType(request); - - ResponseType.should.equal(CodeResponseType); - }); - }); - }); - - describe('buildSuccessRedirectUri()', function() { - it('should return a redirect uri', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var responseType = new CodeResponseType(12345); - var redirectUri = handler.buildSuccessRedirectUri('http://example.com/cb', responseType); - - url.format(redirectUri).should.equal('http://example.com/cb?code=12345'); - }); - }); - - describe('buildErrorRedirectUri()', function() { - it('should set `error_description` if available', function() { - var error = new InvalidClientError('foo bar'); - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var redirectUri = handler.buildErrorRedirectUri('http://example.com/cb', error); - - url.format(redirectUri).should.equal('http://example.com/cb?error=invalid_client&error_description=foo%20bar'); - }); - - it('should return a redirect uri', function() { - var error = new InvalidClientError(); - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var redirectUri = handler.buildErrorRedirectUri('http://example.com/cb', error); - - url.format(redirectUri).should.equal('http://example.com/cb?error=invalid_client&error_description=Bad%20Request'); - }); - }); - - describe('updateResponse()', function() { - it('should set the `location` header', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var response = new Response({ body: {}, headers: {} }); - var uri = url.parse('http://example.com/cb'); - - handler.updateResponse(response, uri, 'foobar'); - - response.get('location').should.equal('http://example.com/cb?state=foobar'); - }); - }); -}); diff --git a/test/integration/handlers/token-handler.spec.ts b/test/integration/handlers/token-handler.spec.ts new file mode 100755 index 000000000..66de911d5 --- /dev/null +++ b/test/integration/handlers/token-handler.spec.ts @@ -0,0 +1,1676 @@ +import * as should from 'should'; +import * as util from 'util'; +import { AccessDeniedError } from '../../../lib/errors/access-denied-error'; +import { InvalidArgumentError } from '../../../lib/errors/invalid-argument-error'; +import { InvalidClientError } from '../../../lib/errors/invalid-client-error'; +import { InvalidRequestError } from '../../../lib/errors/invalid-request-error'; +import { ServerError } from '../../../lib/errors/server-error'; +import { UnauthorizedClientError } from '../../../lib/errors/unauthorized-client-error'; +import { UnsupportedGrantTypeError } from '../../../lib/errors/unsupported-grant-type-error'; +import { PasswordGrantType } from '../../../lib/grant-types/password-grant-type'; +import { TokenHandler } from '../../../lib/handlers/token-handler'; +import { Request } from '../../../lib/request'; +import { Response } from '../../../lib/response'; +import { BearerTokenType } from '../../../lib/token-types/bearer-token-type'; + +/** + * Test `TokenHandler` integration. + */ + +describe('TokenHandler integration', () => { + describe('constructor()', () => { + it('should throw an error if `options.accessTokenLifetime` is missing', () => { + try { + new TokenHandler(); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `accessTokenLifetime`'); + } + }); + + it('should throw an error if `options.model` is missing', () => { + try { + new TokenHandler({ accessTokenLifetime: 120 }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `model`'); + } + }); + + it('should throw an error if `options.refreshTokenLifetime` is missing', () => { + try { + new TokenHandler({ accessTokenLifetime: 120, model: {} }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `refreshTokenLifetime`'); + } + }); + + it('should throw an error if the model does not implement `getClient()`', () => { + try { + new TokenHandler({ + accessTokenLifetime: 120, + model: {}, + refreshTokenLifetime: 120, + }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `getClient()`', + ); + } + }); + + it('should set the `accessTokenLifetime`', () => { + const accessTokenLifetime = {}; + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime, + model, + refreshTokenLifetime: 120, + }); + + handler.accessTokenLifetime.should.equal(accessTokenLifetime); + }); + + it('should set the `alwaysIssueNewRefreshToken`', () => { + const alwaysIssueNewRefreshToken = true; + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 123, + model, + refreshTokenLifetime: 120, + alwaysIssueNewRefreshToken, + }); + + handler.alwaysIssueNewRefreshToken.should.equal( + alwaysIssueNewRefreshToken, + ); + }); + + it('should set the `alwaysIssueNewRefreshToken` to false', () => { + const alwaysIssueNewRefreshToken = false; + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 123, + model, + refreshTokenLifetime: 120, + alwaysIssueNewRefreshToken, + }); + + handler.alwaysIssueNewRefreshToken.should.equal( + alwaysIssueNewRefreshToken, + ); + }); + + it('should return the default `alwaysIssueNewRefreshToken` value', () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 123, + model, + refreshTokenLifetime: 120, + }); + + handler.alwaysIssueNewRefreshToken.should.equal(true); + }); + + it('should set the `extendedGrantTypes`', () => { + const extendedGrantTypes = { foo: 'bar' }; + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + extendedGrantTypes, + model, + refreshTokenLifetime: 120, + }); + + handler.grantTypes.should.containEql(extendedGrantTypes); + }); + + it('should set the `model`', () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + + handler.model.should.equal(model); + }); + + it('should set the `refreshTokenLifetime`', () => { + const refreshTokenLifetime = {}; + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime, + }); + + handler.refreshTokenLifetime.should.equal(refreshTokenLifetime); + }); + }); + + describe('handle()', () => { + it('should throw an error if `request` is missing', async () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + + try { + await handler.handle(undefined, undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: `request` must be an instance of Request', + ); + } + }); + + it('should throw an error if `response` is missing', async () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: {}, + }); + + try { + await handler.handle(request, undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: `response` must be an instance of Response', + ); + } + }); + + it('should throw an error if the method is not `POST`', () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: {}, + headers: {}, + method: 'GET', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Invalid request: method must be POST'); + }); + }); + + it('should throw an error if the media type is not `application/x-www-form-urlencoded`', () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: {}, + headers: {}, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal( + 'Invalid request: content must be application/x-www-form-urlencoded', + ); + }); + }); + + it('should throw the error if an oauth error is thrown', () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: {}, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(InvalidClientError); + e.message.should.equal( + 'Invalid client: cannot retrieve client credentials', + ); + }); + }); + + it('should throw a server error if a non-oauth error is thrown', () => { + const model = { + getClient() { + throw new Error('Unhandled exception'); + }, + getUser() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { + client_id: 12345, + client_secret: 'secret', + grant_type: 'password', + password: 'bar', + username: 'foo', + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(ServerError); + e.message.should.equal('Unhandled exception'); + e.inner.should.be.an.instanceOf(Error); + }); + }); + + it('should update the response if an error is thrown', () => { + const model = { + getClient() { + throw new Error('Unhandled exception'); + }, + getUser() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { + client_id: 12345, + client_secret: 'secret', + grant_type: 'password', + password: 'bar', + username: 'foo', + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(() => { + response.body.should.eql({ + error: 'server_error', + error_description: 'Unhandled exception', + }); + response.status.should.equal(503); + }); + }); + + it('should return a bearer token if successful', async () => { + const token = { + accessToken: 'foo', + client: {}, + refreshToken: 'bar', + scope: 'foobar', + user: {}, + }; + const model = { + getClient() { + return { grants: ['password'] }; + }, + getUser() { + return {}; + }, + saveToken() { + return token; + }, + validateScope() { + return 'baz'; + }, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { + client_id: 12345, + client_secret: 'secret', + username: 'foo', + password: 'bar', + grant_type: 'password', + scope: 'baz', + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + try { + const data = await handler.handle(request, response); + data.should.eql(token); + } catch (error) { + should.fail('should.fail', ''); + } + }); + + it('should not return custom attributes in a bearer token if the allowExtendedTokenAttributes is not set', () => { + const token = { + accessToken: 'foo', + client: {}, + refreshToken: 'bar', + scope: 'foobar', + user: {}, + foo: 'bar', + }; + const model = { + getClient() { + return { grants: ['password'] }; + }, + getUser() { + return {}; + }, + saveToken() { + return token; + }, + validateScope() { + return 'baz'; + }, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { + client_id: 12345, + client_secret: 'secret', + username: 'foo', + password: 'bar', + grant_type: 'password', + scope: 'baz', + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => { + should.exist(response.body.access_token); + should.exist(response.body.refresh_token); + should.exist(response.body.token_type); + should.exist(response.body.scope); + should.not.exist(response.body.foo); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + + it('should return custom attributes in a bearer token if the allowExtendedTokenAttributes is set', () => { + const token = { + accessToken: 'foo', + client: {}, + refreshToken: 'bar', + scope: 'foobar', + user: {}, + foo: 'bar', + }; + const model = { + getClient() { + return { grants: ['password'] }; + }, + getUser() { + return {}; + }, + saveToken() { + return token; + }, + validateScope() { + return 'baz'; + }, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + allowExtendedTokenAttributes: true, + }); + const request = new Request({ + body: { + client_id: 12345, + client_secret: 'secret', + username: 'foo', + password: 'bar', + grant_type: 'password', + scope: 'baz', + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => { + should.exist(response.body.access_token); + should.exist(response.body.refresh_token); + should.exist(response.body.token_type); + should.exist(response.body.scope); + should.exist(response.body.foo); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + + describe('getClient()', () => { + it('should throw an error if `clientId` is invalid', async () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { client_id: 'øå€£‰', client_secret: 'foo' }, + headers: {}, + method: {}, + query: {}, + }); + + try { + await handler.getClient(request, undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Invalid parameter: `client_id`'); + } + }); + + it('should throw an error if `clientSecret` is invalid', async () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { client_id: 'foo', client_secret: 'øå€£‰' }, + headers: {}, + method: {}, + query: {}, + }); + + try { + await handler.getClient(request, undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Invalid parameter: `client_secret`'); + } + }); + + it('should throw an error if `client` is missing', () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: {}, + query: {}, + }); + + return handler + .getClient(request, undefined) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(InvalidClientError); + e.message.should.equal('Invalid client: client is invalid'); + }); + }); + + it('should throw an error if `client.grants` is missing', () => { + const model = { + getClient() { + return {}; + }, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: {}, + query: {}, + }); + + return handler + .getClient(request, undefined) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(ServerError); + e.message.should.equal('Server error: missing client `grants`'); + }); + }); + + it('should throw an error if `client.grants` is invalid', async () => { + const model = { + getClient() { + return { grants: 'foobar' }; + }, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: {}, + query: {}, + }); + try { + await handler.getClient(request, undefined); + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(ServerError); + e.message.should.equal('Server error: `grants` must be an array'); + } + }); + + it('should throw a 401 error if the client is invalid and the request contains an authorization header', () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: {}, + headers: { + authorization: util.format( + 'Basic %s', + Buffer.from('foo:bar').toString('base64'), + ), + }, + method: {}, + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .getClient(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(InvalidClientError); + e.code.should.equal(401); + e.message.should.equal('Invalid client: client is invalid'); + + response + .get('WWW-Authenticate') + .should.equal('Basic realm="Service"'); + }); + }); + + it('should return a client', async () => { + const client = { id: 12345, grants: [] }; + const model = { + getClient() { + return client; + }, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: {}, + query: {}, + }); + try { + const data = await handler.getClient(request, undefined); + data.should.equal(client); + } catch (error) { + should.fail('should.fail', ''); + } + }); + + describe('with `password` grant type and `requireClientAuthentication` is false', () => { + it('should return a client ', () => { + const client = { id: 12345, grants: [] }; + const model = { + async getClient() { + return client; + }, + async saveToken() {}, + }; + + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + requireClientAuthentication: { + password: false, + }, + }); + const request = new Request({ + body: { client_id: 'blah', grant_type: 'password' }, + headers: {}, + method: {}, + query: {}, + }); + + return handler + .getClient(request, undefined) + .then(data => { + data.should.equal(client); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + + describe('with `password` grant type and `requireClientAuthentication` is false and Authorization header', () => { + it('should return a client ', () => { + const client = { id: 12345, grants: [] }; + const model = { + async getClient() { + return client; + }, + async saveToken() {}, + }; + + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + requireClientAuthentication: { + password: false, + }, + }); + const request = new Request({ + body: { grant_type: 'password' }, + headers: { + authorization: util.format( + 'Basic %s', + Buffer.from('blah:').toString('base64'), + ), + }, + method: {}, + query: {}, + }); + + return handler + .getClient(request, undefined) + .then(data => { + data.should.equal(client); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + + it('should support promises', () => { + const model = { + getClient() { + return Promise.resolve({ grants: [] }); + }, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: {}, + query: {}, + }); + + handler.getClient(request, undefined).should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const model = { + getClient() { + return { grants: [] }; + }, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: {}, + query: {}, + }); + + handler.getClient(request, undefined).should.be.an.instanceOf(Promise); + }); + + /* it('should support callbacks', () => { + const model = { + getClient(clientId, clientSecret, callback) { + callback(null, { grants: [] }); + }, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: {}, + query: {}, + }); + + handler.getClient(request, undefined).should.be.an.instanceOf(Promise); + }); */ + }); + + describe('getClientCredentials()', () => { + it('should throw an error if `client_id` is missing', () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { client_secret: 'foo' }, + headers: {}, + method: {}, + query: {}, + }); + + try { + handler.getClientCredentials(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidClientError); + e.message.should.equal( + 'Invalid client: cannot retrieve client credentials', + ); + } + }); + + it('should throw an error if `client_secret` is missing', () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { client_id: 'foo' }, + headers: {}, + method: {}, + query: {}, + }); + + try { + handler.getClientCredentials(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidClientError); + e.message.should.equal( + 'Invalid client: cannot retrieve client credentials', + ); + } + }); + + describe('with `client_id` and grant type is `password` and `requireClientAuthentication` is false', () => { + it('should return a client', () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + requireClientAuthentication: { password: false }, + }); + const request = new Request({ + body: { client_id: 'foo', grant_type: 'password' }, + headers: {}, + method: {}, + query: {}, + }); + const credentials = handler.getClientCredentials(request); + + credentials.should.eql({ clientId: 'foo' }); + }); + }); + + describe('with `client_id` and `client_secret` in the request header as basic auth', () => { + it('should return a client', () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: {}, + headers: { + authorization: util.format( + 'Basic %s', + Buffer.from('foo:bar').toString('base64'), + ), + }, + method: {}, + query: {}, + }); + const credentials = handler.getClientCredentials(request); + + credentials.should.eql({ clientId: 'foo', clientSecret: 'bar' }); + }); + }); + + describe('with `client_id` and `client_secret` in the request body', () => { + it('should return a client', () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { client_id: 'foo', client_secret: 'bar' }, + headers: {}, + method: {}, + query: {}, + }); + const credentials = handler.getClientCredentials(request); + + credentials.should.eql({ clientId: 'foo', clientSecret: 'bar' }); + }); + }); + }); + + describe('handleGrantType()', () => { + it('should throw an error if `grant_type` is missing', async () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: {}, + }); + + try { + await handler.handleGrantType(request, undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Missing parameter: `grant_type`'); + } + }); + + it('should throw an error if `grant_type` is invalid', async () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { grant_type: '~foo~' }, + headers: {}, + method: {}, + query: {}, + }); + + try { + await handler.handleGrantType(request, undefined); + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Invalid parameter: `grant_type`'); + } + }); + + it('should throw an error if `grant_type` is unsupported', async () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { grant_type: 'foobar' }, + headers: {}, + method: {}, + query: {}, + }); + + try { + await handler.handleGrantType(request, undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(UnsupportedGrantTypeError); + e.message.should.equal( + 'Unsupported grant type: `grant_type` is invalid', + ); + } + }); + + it('should throw an error if `grant_type` is unauthorized', async () => { + const client = { grants: ['client_credentials'] }; + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { grant_type: 'password' }, + headers: {}, + method: {}, + query: {}, + }); + + try { + await handler.handleGrantType(request, client); + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(UnauthorizedClientError); + e.message.should.equal('Unauthorized client: `grant_type` is invalid'); + } + }); + + /* it('should throw an invalid grant error if a non-oauth error is thrown', () => { + const client = { grants: ['password'] }; + const model = { + getClient(clientId, password, callback) { + callback(null, client); + }, + getUser(uid, pwd, callback) { + callback(); + }, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { grant_type: 'password', username: 'foo', password: 'bar' }, + headers: {}, + method: {}, + query: {}, + }); + + return handler + .handleGrantType(request, client) + .then(() => should.fail('should.fail', '')) + .catch(e => { + e.should.be.an.instanceOf(InvalidGrantError); + e.message.should.equal('Invalid grant: user credentials are invalid'); + }); + }); */ + + describe('with grant_type `authorization_code`', () => { + it('should return a token', () => { + const client = { id: 'foobar', grants: ['authorization_code'] }; + const token = {}; + const model = { + getAuthorizationCode() { + return { + authorizationCode: 12345, + client: { id: 'foobar' }, + expiresAt: new Date(new Date().getTime() * 2), + user: {}, + }; + }, + getClient() {}, + saveToken() { + return token; + }, + validateScope() { + return 'foo'; + }, + revokeAuthorizationCode() { + return { + authorizationCode: 12345, + client: { id: 'foobar' }, + expiresAt: new Date(new Date().getTime() / 2), + user: {}, + }; + }, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { + code: 12345, + grant_type: 'authorization_code', + }, + headers: {}, + method: {}, + query: {}, + }); + + return handler + .handleGrantType(request, client) + .then(data => { + data.should.equal(token); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + + describe('with grant_type `client_credentials`', () => { + it('should return a token', () => { + const client = { grants: ['client_credentials'] }; + const token = {}; + const model = { + getClient() {}, + getUserFromClient() { + return {}; + }, + saveToken() { + return token; + }, + validateScope() { + return 'foo'; + }, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { + grant_type: 'client_credentials', + scope: 'foo', + }, + headers: {}, + method: {}, + query: {}, + }); + + return handler + .handleGrantType(request, client) + .then(data => { + data.should.equal(token); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + + describe('with grant_type `password`', () => { + it('should return a token', () => { + const client = { grants: ['password'] }; + const token = {}; + const model = { + getClient() {}, + getUser() { + return {}; + }, + saveToken() { + return token; + }, + validateScope() { + return 'baz'; + }, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { + client_id: 12345, + client_secret: 'secret', + grant_type: 'password', + password: 'bar', + username: 'foo', + scope: 'baz', + }, + headers: {}, + method: {}, + query: {}, + }); + + return handler + .handleGrantType(request, client) + .then(data => { + data.should.equal(token); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + + describe('with grant_type `refresh_token`', () => { + it('should return a token', () => { + const client = { grants: ['refresh_token'] }; + const token = { accessToken: 'foo', client: {}, user: {} }; + const model = { + getClient() {}, + getRefreshToken() { + return { + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date().getTime() * 2), + user: {}, + }; + }, + saveToken() { + return token; + }, + revokeToken() { + return { + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date().getTime() / 2), + user: {}, + }; + }, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { + grant_type: 'refresh_token', + refresh_token: 12345, + }, + headers: {}, + method: {}, + query: {}, + }); + + return handler + .handleGrantType(request, client) + .then(data => { + data.should.equal(token); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + + describe('with custom grant_type', () => { + it('should return a token', () => { + const client = { + grants: ['urn:ietf:params:oauth:grant-type:saml2-bearer'], + }; + const token = {}; + const model = { + getClient() {}, + getUser() { + return {}; + }, + saveToken() { + return token; + }, + validateScope() { + return 'foo'; + }, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + extendedGrantTypes: { + 'urn:ietf:params:oauth:grant-type:saml2-bearer': PasswordGrantType, + }, + }); + const request = new Request({ + body: { + grant_type: 'urn:ietf:params:oauth:grant-type:saml2-bearer', + username: 'foo', + password: 'bar', + }, + headers: {}, + method: {}, + query: {}, + }); + + return handler + .handleGrantType(request, client) + .then(data => { + data.should.equal(token); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + }); + + describe('getAccessTokenLifetime()', () => { + it('should return the client access token lifetime', () => { + const client = { accessTokenLifetime: 60 }; + const model = { + getClient() { + return client; + }, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + + handler.getAccessTokenLifetime(client).should.equal(60); + }); + + it('should return the default access token lifetime', () => { + const client = {}; + const model = { + getClient() { + return client; + }, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + + handler.getAccessTokenLifetime(client).should.equal(120); + }); + }); + + describe('getRefreshTokenLifetime()', () => { + it('should return the client access token lifetime', () => { + const client = { refreshTokenLifetime: 60 }; + const model = { + getClient() { + return client; + }, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + + handler.getRefreshTokenLifetime(client).should.equal(60); + }); + + it('should return the default access token lifetime', () => { + const client = {}; + const model = { + getClient() { + return client; + }, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + + handler.getRefreshTokenLifetime(client).should.equal(120); + }); + }); + + describe('getTokenType()', () => { + it('should return a token type', () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const tokenType = handler.getTokenType({ + accessToken: 'foo', + refreshToken: 'bar', + scope: 'foobar', + }); + + tokenType.should.containEql({ + accessToken: 'foo', + accessTokenLifetime: undefined, + refreshToken: 'bar', + scope: 'foobar', + }); + }); + }); + + describe('updateSuccessResponse()', () => { + it('should set the `body`', () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const tokenType = new BearerTokenType( + 'foo', + 'bar' as any, + 'biz', + undefined, + undefined, + ); + const response = new Response({ body: {}, headers: {} }); + + handler.updateSuccessResponse(response, tokenType); + + response.body.should.eql({ + access_token: 'foo', + expires_in: 'bar', + refresh_token: 'biz', + token_type: 'Bearer', + }); + }); + + it('should set the `Cache-Control` header', () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const tokenType = new BearerTokenType( + 'foo', + 'bar' as any, + 'biz', + undefined, + undefined, + ); + const response = new Response({ body: {}, headers: {} }); + + handler.updateSuccessResponse(response, tokenType); + + response.get('Cache-Control').should.equal('no-store'); + }); + + it('should set the `Pragma` header', () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const tokenType = new BearerTokenType( + 'foo', + 'bar' as any, + 'biz', + undefined, + undefined, + ); + const response = new Response({ body: {}, headers: {} }); + + handler.updateSuccessResponse(response, tokenType); + + response.get('Pragma').should.equal('no-cache'); + }); + }); + + describe('updateErrorResponse()', () => { + it('should set the `body`', () => { + const error = new AccessDeniedError('Cannot request a token'); + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const response = new Response({ body: {}, headers: {} }); + + handler.updateErrorResponse(response, error); + + response.body.error.should.equal('access_denied'); + response.body.error_description.should.equal('Cannot request a token'); + }); + + it('should set the `status`', () => { + const error = new AccessDeniedError('Cannot request a token'); + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const response = new Response({ body: {}, headers: {} }); + + handler.updateErrorResponse(response, error); + + response.status.should.equal(400); + }); + }); +}); diff --git a/test/integration/handlers/token-handler_test.js b/test/integration/handlers/token-handler_test.js deleted file mode 100644 index 50277c113..000000000 --- a/test/integration/handlers/token-handler_test.js +++ /dev/null @@ -1,1079 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var AccessDeniedError = require('../../../lib/errors/access-denied-error'); -var BearerTokenType = require('../../../lib/token-types/bearer-token-type'); -var InvalidArgumentError = require('../../../lib/errors/invalid-argument-error'); -var InvalidClientError = require('../../../lib/errors/invalid-client-error'); -var InvalidGrantError = require('../../../lib/errors/invalid-grant-error'); -var InvalidRequestError = require('../../../lib/errors/invalid-request-error'); -var PasswordGrantType = require('../../../lib/grant-types/password-grant-type'); -var Promise = require('bluebird'); -var Request = require('../../../lib/request'); -var Response = require('../../../lib/response'); -var ServerError = require('../../../lib/errors/server-error'); -var TokenHandler = require('../../../lib/handlers/token-handler'); -var UnauthorizedClientError = require('../../../lib/errors/unauthorized-client-error'); -var UnsupportedGrantTypeError = require('../../../lib/errors/unsupported-grant-type-error'); -var should = require('should'); -var util = require('util'); - -/** - * Test `TokenHandler` integration. - */ - -describe('TokenHandler integration', function() { - describe('constructor()', function() { - it('should throw an error if `options.accessTokenLifetime` is missing', function() { - try { - new TokenHandler(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `accessTokenLifetime`'); - } - }); - - it('should throw an error if `options.model` is missing', function() { - try { - new TokenHandler({ accessTokenLifetime: 120 }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `model`'); - } - }); - - it('should throw an error if `options.refreshTokenLifetime` is missing', function() { - try { - new TokenHandler({ accessTokenLifetime: 120, model: {} }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `refreshTokenLifetime`'); - } - }); - - it('should throw an error if the model does not implement `getClient()`', function() { - try { - new TokenHandler({ accessTokenLifetime: 120, model: {}, refreshTokenLifetime: 120 }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: model does not implement `getClient()`'); - } - }); - - it('should set the `accessTokenLifetime`', function() { - var accessTokenLifetime = {}; - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: accessTokenLifetime, model: model, refreshTokenLifetime: 120 }); - - handler.accessTokenLifetime.should.equal(accessTokenLifetime); - }); - - it('should set the `alwaysIssueNewRefreshToken`', function() { - var alwaysIssueNewRefreshToken = true; - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 123, model: model, refreshTokenLifetime: 120, alwaysIssueNewRefreshToken: alwaysIssueNewRefreshToken }); - - handler.alwaysIssueNewRefreshToken.should.equal(alwaysIssueNewRefreshToken); - }); - - it('should set the `alwaysIssueNewRefreshToken` to false', function() { - var alwaysIssueNewRefreshToken = false; - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 123, model: model, refreshTokenLifetime: 120, alwaysIssueNewRefreshToken: alwaysIssueNewRefreshToken }); - - handler.alwaysIssueNewRefreshToken.should.equal(alwaysIssueNewRefreshToken); - }); - - it('should return the default `alwaysIssueNewRefreshToken` value', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 123, model: model, refreshTokenLifetime: 120 }); - - handler.alwaysIssueNewRefreshToken.should.equal(true); - }); - - it('should set the `extendedGrantTypes`', function() { - var extendedGrantTypes = { foo: 'bar' }; - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, extendedGrantTypes: extendedGrantTypes, model: model, refreshTokenLifetime: 120 }); - - handler.grantTypes.should.containEql(extendedGrantTypes); - }); - - it('should set the `model`', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - - handler.model.should.equal(model); - }); - - it('should set the `refreshTokenLifetime`', function() { - var refreshTokenLifetime = {}; - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: refreshTokenLifetime }); - - handler.refreshTokenLifetime.should.equal(refreshTokenLifetime); - }); - }); - - describe('handle()', function() { - it('should throw an error if `request` is missing', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - - try { - handler.handle(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: `request` must be an instance of Request'); - } - }); - - it('should throw an error if `response` is missing', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - try { - handler.handle(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: `response` must be an instance of Response'); - } - }); - - it('should throw an error if the method is not `POST`', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: {}, headers: {}, method: 'GET', query: {} }); - var response = new Response({ body: {}, headers: {} }); - - return handler.handle(request, response) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Invalid request: method must be POST'); - }); - }); - - it('should throw an error if the media type is not `application/x-www-form-urlencoded`', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: {}, headers: {}, method: 'POST', query: {} }); - var response = new Response({ body: {}, headers: {} }); - - return handler.handle(request, response) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Invalid request: content must be application/x-www-form-urlencoded'); - }); - }); - - it('should throw the error if an oauth error is thrown', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: {}, headers: { 'content-type': 'application/x-www-form-urlencoded', 'transfer-encoding': 'chunked' }, method: 'POST', query: {} }); - var response = new Response({ body: {}, headers: {} }); - - return handler.handle(request, response) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidClientError); - e.message.should.equal('Invalid client: cannot retrieve client credentials'); - }); - }); - - it('should throw a server error if a non-oauth error is thrown', function() { - var model = { - getClient: function() { - throw new Error('Unhandled exception'); - }, - getUser: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ - body: { - client_id: 12345, - client_secret: 'secret', - grant_type: 'password', - password: 'bar', - username: 'foo' - }, - headers: { 'content-type': 'application/x-www-form-urlencoded', 'transfer-encoding': 'chunked' }, - method: 'POST', - query: {} - }); - var response = new Response({ body: {}, headers: {} }); - - return handler.handle(request, response) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(ServerError); - e.message.should.equal('Unhandled exception'); - e.inner.should.be.an.instanceOf(Error); - }); - }); - - it('should update the response if an error is thrown', function() { - var model = { - getClient: function() { - throw new Error('Unhandled exception'); - }, - getUser: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ - body: { - client_id: 12345, - client_secret: 'secret', - grant_type: 'password', - password: 'bar', - username: 'foo' - }, - headers: { 'content-type': 'application/x-www-form-urlencoded', 'transfer-encoding': 'chunked' }, - method: 'POST', - query: {} - }); - var response = new Response({ body: {}, headers: {} }); - - return handler.handle(request, response) - .then(should.fail) - .catch(function() { - response.body.should.eql({ error: 'server_error', error_description: 'Unhandled exception' }); - response.status.should.equal(503); - }); - }); - - it('should return a bearer token if successful', function() { - var token = { accessToken: 'foo', client: {}, refreshToken: 'bar', scope: 'foobar', user: {} }; - var model = { - getClient: function() { return { grants: ['password'] }; }, - getUser: function() { return {}; }, - saveToken: function() { return token; }, - validateScope: function() { return 'baz'; } - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ - body: { - client_id: 12345, - client_secret: 'secret', - username: 'foo', - password: 'bar', - grant_type: 'password', - scope: 'baz' - }, - headers: { 'content-type': 'application/x-www-form-urlencoded', 'transfer-encoding': 'chunked' }, - method: 'POST', - query: {} - }); - var response = new Response({ body: {}, headers: {} }); - - return handler.handle(request, response) - .then(function(data) { - data.should.eql(token); - }) - .catch(should.fail); - }); - - it('should not return custom attributes in a bearer token if the allowExtendedTokenAttributes is not set', function() { - var token = { accessToken: 'foo', client: {}, refreshToken: 'bar', scope: 'foobar', user: {}, foo: 'bar' }; - var model = { - getClient: function() { return { grants: ['password'] }; }, - getUser: function() { return {}; }, - saveToken: function() { return token; }, - validateScope: function() { return 'baz'; } - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ - body: { - client_id: 12345, - client_secret: 'secret', - username: 'foo', - password: 'bar', - grant_type: 'password', - scope: 'baz' - }, - headers: { 'content-type': 'application/x-www-form-urlencoded', 'transfer-encoding': 'chunked' }, - method: 'POST', - query: {} - }); - var response = new Response({ body: {}, headers: {} }); - - return handler.handle(request, response) - .then(function() { - should.exist(response.body.access_token); - should.exist(response.body.refresh_token); - should.exist(response.body.token_type); - should.exist(response.body.scope); - should.not.exist(response.body.foo); - }) - .catch(should.fail); - }); - - it('should return custom attributes in a bearer token if the allowExtendedTokenAttributes is set', function() { - var token = { accessToken: 'foo', client: {}, refreshToken: 'bar', scope: 'foobar', user: {}, foo: 'bar' }; - var model = { - getClient: function() { return { grants: ['password'] }; }, - getUser: function() { return {}; }, - saveToken: function() { return token; }, - validateScope: function() { return 'baz'; } - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120, allowExtendedTokenAttributes: true }); - var request = new Request({ - body: { - client_id: 12345, - client_secret: 'secret', - username: 'foo', - password: 'bar', - grant_type: 'password', - scope: 'baz' - }, - headers: { 'content-type': 'application/x-www-form-urlencoded', 'transfer-encoding': 'chunked' }, - method: 'POST', - query: {} - }); - var response = new Response({ body: {}, headers: {} }); - - return handler.handle(request, response) - .then(function() { - should.exist(response.body.access_token); - should.exist(response.body.refresh_token); - should.exist(response.body.token_type); - should.exist(response.body.scope); - should.exist(response.body.foo); - }) - .catch(should.fail); - }); - }); - - - describe('getClient()', function() { - it('should throw an error if `clientId` is invalid', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { client_id: 'øå€£‰', client_secret: 'foo' }, headers: {}, method: {}, query: {} }); - - try { - handler.getClient(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Invalid parameter: `client_id`'); - } - }); - - it('should throw an error if `clientSecret` is invalid', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { client_id: 'foo', client_secret: 'øå€£‰' }, headers: {}, method: {}, query: {} }); - - try { - handler.getClient(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Invalid parameter: `client_secret`'); - } - }); - - it('should throw an error if `client` is missing', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, method: {}, query: {} }); - - return handler.getClient(request) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidClientError); - e.message.should.equal('Invalid client: client is invalid'); - }); - }); - - it('should throw an error if `client.grants` is missing', function() { - var model = { - getClient: function() { return {}; }, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, method: {}, query: {} }); - - return handler.getClient(request) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(ServerError); - e.message.should.equal('Server error: missing client `grants`'); - }); - }); - - it('should throw an error if `client.grants` is invalid', function() { - var model = { - getClient: function() { return { grants: 'foobar' }; }, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, method: {}, query: {} }); - - return handler.getClient(request) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(ServerError); - e.message.should.equal('Server error: `grants` must be an array'); - }); - }); - - it('should throw a 401 error if the client is invalid and the request contains an authorization header', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ - body: {}, - headers: { 'authorization': util.format('Basic %s', new Buffer('foo:bar').toString('base64')) }, - method: {}, - query: {} - }); - var response = new Response({ body: {}, headers: {} }); - - return handler.getClient(request, response) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidClientError); - e.code.should.equal(401); - e.message.should.equal('Invalid client: client is invalid'); - - response.get('WWW-Authenticate').should.equal('Basic realm="Service"'); - }); - }); - - it('should return a client', function() { - var client = { id: 12345, grants: [] }; - var model = { - getClient: function() { return client; }, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, method: {}, query: {} }); - - return handler.getClient(request) - .then(function(data) { - data.should.equal(client); - }) - .catch(should.fail); - }); - - describe('with `password` grant type and `requireClientAuthentication` is false', function() { - - it('should return a client ', function() { - var client = { id: 12345, grants: [] }; - var model = { - getClient: function() { return client; }, - saveToken: function() {} - }; - - var handler = new TokenHandler({ - accessTokenLifetime: 120, - model: model, - refreshTokenLifetime: 120, - requireClientAuthentication: { - password: false - } - }); - var request = new Request({ body: { client_id: 'blah', grant_type: 'password'}, headers: {}, method: {}, query: {} }); - - return handler.getClient(request) - .then(function(data) { - data.should.equal(client); - }) - .catch(should.fail); - }); - }); - - describe('with `password` grant type and `requireClientAuthentication` is false and Authorization header', function() { - - it('should return a client ', function() { - var client = { id: 12345, grants: [] }; - var model = { - getClient: function() { return client; }, - saveToken: function() {} - }; - - var handler = new TokenHandler({ - accessTokenLifetime: 120, - model: model, - refreshTokenLifetime: 120, - requireClientAuthentication: { - password: false - } - }); - var request = new Request({ - body: { grant_type: 'password'}, - headers: { 'authorization': util.format('Basic %s', new Buffer('blah:').toString('base64')) }, - method: {}, - query: {} - }); - - return handler.getClient(request) - .then(function(data) { - data.should.equal(client); - }) - .catch(should.fail); - }); - }); - - it('should support promises', function() { - var model = { - getClient: function() { return Promise.resolve({ grants: [] }); }, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, method: {}, query: {} }); - - handler.getClient(request).should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var model = { - getClient: function() { return { grants: [] }; }, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, method: {}, query: {} }); - - handler.getClient(request).should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function() { - var model = { - getClient: function(clientId, clientSecret, callback) { callback(null, { grants: [] }); }, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, method: {}, query: {} }); - - handler.getClient(request).should.be.an.instanceOf(Promise); - }); - }); - - describe('getClientCredentials()', function() { - it('should throw an error if `client_id` is missing', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { client_secret: 'foo' }, headers: {}, method: {}, query: {} }); - - try { - handler.getClientCredentials(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidClientError); - e.message.should.equal('Invalid client: cannot retrieve client credentials'); - } - }); - - it('should throw an error if `client_secret` is missing', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { client_id: 'foo' }, headers: {}, method: {}, query: {} }); - - try { - handler.getClientCredentials(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidClientError); - e.message.should.equal('Invalid client: cannot retrieve client credentials'); - } - }); - - describe('with `client_id` and grant type is `password` and `requireClientAuthentication` is false', function() { - it('should return a client', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120, requireClientAuthentication: { password: false} }); - var request = new Request({ body: { client_id: 'foo', grant_type: 'password' }, headers: {}, method: {}, query: {} }); - var credentials = handler.getClientCredentials(request); - - credentials.should.eql({ clientId: 'foo' }); - }); - }); - - describe('with `client_id` and `client_secret` in the request header as basic auth', function() { - it('should return a client', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ - body: {}, - headers: { - 'authorization': util.format('Basic %s', new Buffer('foo:bar').toString('base64')) - }, - method: {}, - query: {} - }); - var credentials = handler.getClientCredentials(request); - - credentials.should.eql({ clientId: 'foo', clientSecret: 'bar' }); - }); - }); - - describe('with `client_id` and `client_secret` in the request body', function() { - it('should return a client', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { client_id: 'foo', client_secret: 'bar' }, headers: {}, method: {}, query: {} }); - var credentials = handler.getClientCredentials(request); - - credentials.should.eql({ clientId: 'foo', clientSecret: 'bar' }); - }); - }); - }); - - describe('handleGrantType()', function() { - it('should throw an error if `grant_type` is missing', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - try { - handler.handleGrantType(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Missing parameter: `grant_type`'); - } - }); - - it('should throw an error if `grant_type` is invalid', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { grant_type: '~foo~' }, headers: {}, method: {}, query: {} }); - - try { - handler.handleGrantType(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Invalid parameter: `grant_type`'); - } - }); - - it('should throw an error if `grant_type` is unsupported', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { grant_type: 'foobar' }, headers: {}, method: {}, query: {} }); - - try { - handler.handleGrantType(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(UnsupportedGrantTypeError); - e.message.should.equal('Unsupported grant type: `grant_type` is invalid'); - } - }); - - it('should throw an error if `grant_type` is unauthorized', function() { - var client = { grants: ['client_credentials'] }; - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { grant_type: 'password' }, headers: {}, method: {}, query: {} }); - - try { - handler.handleGrantType(request, client); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(UnauthorizedClientError); - e.message.should.equal('Unauthorized client: `grant_type` is invalid'); - } - }); - - it('should throw an invalid grant error if a non-oauth error is thrown', function() { - var client = { grants: ['password'] }; - var model = { - getClient: function(clientId, password, callback) { callback(null, client); }, - getUser: function(uid, pwd, callback) { callback(); }, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { grant_type: 'password', username: 'foo', password: 'bar' }, headers: {}, method: {}, query: {} }); - - return handler.handleGrantType(request, client) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidGrantError); - e.message.should.equal('Invalid grant: user credentials are invalid'); - }); - }); - - describe('with grant_type `authorization_code`', function() { - it('should return a token', function() { - var client = { id: 'foobar', grants: ['authorization_code'] }; - var token = {}; - var model = { - getAuthorizationCode: function() { return { authorizationCode: 12345, client: { id: 'foobar' }, expiresAt: new Date(new Date() * 2), user: {} }; }, - getClient: function() {}, - saveToken: function() { return token; }, - validateScope: function() { return 'foo'; }, - revokeAuthorizationCode: function() { return { authorizationCode: 12345, client: { id: 'foobar' }, expiresAt: new Date(new Date() / 2), user: {} }; } - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ - body: { - code: 12345, - grant_type: 'authorization_code' - }, - headers: {}, - method: {}, - query: {} - }); - - return handler.handleGrantType(request, client) - .then(function(data) { - data.should.equal(token); - }) - .catch(should.fail); - }); - }); - - describe('with grant_type `client_credentials`', function() { - it('should return a token', function() { - var client = { grants: ['client_credentials'] }; - var token = {}; - var model = { - getClient: function() {}, - getUserFromClient: function() { return {}; }, - saveToken: function() { return token; }, - validateScope: function() { return 'foo'; } - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ - body: { - grant_type: 'client_credentials', - scope: 'foo' - }, - headers: {}, - method: {}, - query: {} - }); - - return handler.handleGrantType(request, client) - .then(function(data) { - data.should.equal(token); - }) - .catch(should.fail); - }); - }); - - describe('with grant_type `password`', function() { - it('should return a token', function() { - var client = { grants: ['password'] }; - var token = {}; - var model = { - getClient: function() {}, - getUser: function() { return {}; }, - saveToken: function() { return token; }, - validateScope: function() { return 'baz'; } - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ - body: { - client_id: 12345, - client_secret: 'secret', - grant_type: 'password', - password: 'bar', - username: 'foo', - scope: 'baz' - }, - headers: {}, - method: {}, - query: {} - }); - - return handler.handleGrantType(request, client) - .then(function(data) { - data.should.equal(token); - }) - .catch(should.fail); - }); - }); - - describe('with grant_type `refresh_token`', function() { - it('should return a token', function() { - var client = { grants: ['refresh_token'] }; - var token = { accessToken: 'foo', client: {}, user: {} }; - var model = { - getClient: function() {}, - getRefreshToken: function() { return { accessToken: 'foo', client: {}, refreshTokenExpiresAt: new Date(new Date() * 2), user: {} }; }, - saveToken: function() { return token; }, - revokeToken: function() { return { accessToken: 'foo', client: {}, refreshTokenExpiresAt: new Date(new Date() / 2), user: {} }; } - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ - body: { - grant_type: 'refresh_token', - refresh_token: 12345 - }, - headers: {}, - method: {}, - query: {} - }); - - return handler.handleGrantType(request, client) - .then(function(data) { - data.should.equal(token); - }) - .catch(should.fail); - }); - }); - - describe('with custom grant_type', function() { - it('should return a token', function() { - var client = { grants: ['urn:ietf:params:oauth:grant-type:saml2-bearer'] }; - var token = {}; - var model = { - getClient: function() {}, - getUser: function() { return {}; }, - saveToken: function() { return token; }, - validateScope: function() { return 'foo'; } - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120, extendedGrantTypes: { 'urn:ietf:params:oauth:grant-type:saml2-bearer': PasswordGrantType } }); - var request = new Request({ body: { grant_type: 'urn:ietf:params:oauth:grant-type:saml2-bearer', username: 'foo', password: 'bar' }, headers: {}, method: {}, query: {} }); - - return handler.handleGrantType(request, client) - .then(function(data) { - data.should.equal(token); - }) - .catch(should.fail); - }); - }); - }); - - describe('getAccessTokenLifetime()', function() { - it('should return the client access token lifetime', function() { - var client = { accessTokenLifetime: 60 }; - var model = { - getClient: function() { return client; }, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - - handler.getAccessTokenLifetime(client).should.equal(60); - }); - - it('should return the default access token lifetime', function() { - var client = {}; - var model = { - getClient: function() { return client; }, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - - handler.getAccessTokenLifetime(client).should.equal(120); - }); - }); - - describe('getRefreshTokenLifetime()', function() { - it('should return the client access token lifetime', function() { - var client = { refreshTokenLifetime: 60 }; - var model = { - getClient: function() { return client; }, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - - handler.getRefreshTokenLifetime(client).should.equal(60); - }); - - it('should return the default access token lifetime', function() { - var client = {}; - var model = { - getClient: function() { return client; }, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - - handler.getRefreshTokenLifetime(client).should.equal(120); - }); - }); - - describe('getTokenType()', function() { - it('should return a token type', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var tokenType = handler.getTokenType({ accessToken: 'foo', refreshToken: 'bar', scope: 'foobar' }); - - tokenType.should.containEql({ accessToken: 'foo', accessTokenLifetime: undefined, refreshToken: 'bar', scope: 'foobar' }); - }); - }); - - describe('updateSuccessResponse()', function() { - it('should set the `body`', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var tokenType = new BearerTokenType('foo', 'bar', 'biz'); - var response = new Response({ body: {}, headers: {} }); - - handler.updateSuccessResponse(response, tokenType); - - response.body.should.eql({ access_token: 'foo', expires_in: 'bar', refresh_token: 'biz', token_type: 'Bearer' }); - }); - - it('should set the `Cache-Control` header', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var tokenType = new BearerTokenType('foo', 'bar', 'biz'); - var response = new Response({ body: {}, headers: {} }); - - handler.updateSuccessResponse(response, tokenType); - - response.get('Cache-Control').should.equal('no-store'); - }); - - it('should set the `Pragma` header', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var tokenType = new BearerTokenType('foo', 'bar', 'biz'); - var response = new Response({ body: {}, headers: {} }); - - handler.updateSuccessResponse(response, tokenType); - - response.get('Pragma').should.equal('no-cache'); - }); - }); - - describe('updateErrorResponse()', function() { - it('should set the `body`', function() { - var error = new AccessDeniedError('Cannot request a token'); - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var response = new Response({ body: {}, headers: {} }); - - handler.updateErrorResponse(response, error); - - response.body.error.should.equal('access_denied'); - response.body.error_description.should.equal('Cannot request a token'); - }); - - it('should set the `status`', function() { - var error = new AccessDeniedError('Cannot request a token'); - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var response = new Response({ body: {}, headers: {} }); - - handler.updateErrorResponse(response, error); - - response.status.should.equal(400); - }); - }); -}); diff --git a/test/integration/request.spec.ts b/test/integration/request.spec.ts new file mode 100755 index 000000000..81c08ebd0 --- /dev/null +++ b/test/integration/request.spec.ts @@ -0,0 +1,183 @@ +import * as should from 'should'; +import { InvalidArgumentError } from '../../lib/errors/invalid-argument-error'; +import { Request } from '../../lib/request'; + +/** + * Test `Request` integration. + */ + +describe('Request integration', () => { + describe('constructor()', () => { + it('should throw an error if `headers` is missing', () => { + try { + new Request({ body: {} }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `headers`'); + } + }); + + it('should throw an error if `method` is missing', () => { + try { + new Request({ body: {}, headers: {} }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `method`'); + } + }); + + it('should throw an error if `query` is missing', () => { + try { + new Request({ body: {}, headers: {}, method: {} }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `query`'); + } + }); + + it('should set the `body`', () => { + const request = new Request({ + body: 'foo', + headers: {}, + method: {}, + query: {}, + }); + + request.body.should.equal('foo'); + }); + + it('should set the `headers`', () => { + const request = new Request({ + body: {}, + headers: { foo: 'bar', QuX: 'biz' }, + method: {}, + query: {}, + }); + + request.headers.should.eql({ foo: 'bar', qux: 'biz' }); + }); + + it('should set the `method`', () => { + const request = new Request({ + body: {}, + headers: {}, + method: 'biz', + query: {}, + }); + + request.method.should.equal('biz'); + }); + + it('should set the `query`', () => { + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: 'baz', + }); + + request.query.should.equal('baz'); + }); + }); + + describe('get()', () => { + it('should return `undefined` if the field does not exist', () => { + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: {}, + }); + + (request.get('content-type') === undefined).should.be.true(); + }); + + it('should return the value if the field exists', () => { + const request = new Request({ + body: {}, + headers: { + 'content-type': 'text/html; charset=utf-8', + }, + method: {}, + query: {}, + }); + + request.get('Content-Type').should.equal('text/html; charset=utf-8'); + }); + }); + + describe('is()', () => { + it('should accept an array of `types`', () => { + const request = new Request({ + body: {}, + headers: { + 'content-type': 'application/json', + 'transfer-encoding': 'chunked', + }, + method: {}, + query: {}, + }); + + request.is(['html', 'json']).should.equal('json'); + }); + + it('should accept multiple `types` as arguments', () => { + const request = new Request({ + body: {}, + headers: { + 'content-type': 'application/json', + 'transfer-encoding': 'chunked', + }, + method: {}, + query: {}, + }); + + request.is('html', 'json').should.equal('json'); + }); + + it('should return the first matching type', () => { + const request = new Request({ + body: {}, + headers: { + 'content-type': 'text/html; charset=utf-8', + 'transfer-encoding': 'chunked', + }, + method: {}, + query: {}, + }); + + request.is('html').should.equal('html'); + }); + + it('should return `false` if none of the `types` match', () => { + const request = new Request({ + body: {}, + headers: { + 'content-type': 'text/html; charset=utf-8', + 'transfer-encoding': 'chunked', + }, + method: {}, + query: {}, + }); + + request.is('json').should.be.false(); + }); + + it('should return `false` if the request has no body', () => { + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: {}, + }); + + request.is('text/html').should.be.false(); + }); + }); +}); diff --git a/test/integration/request_test.js b/test/integration/request_test.js deleted file mode 100644 index a43527671..000000000 --- a/test/integration/request_test.js +++ /dev/null @@ -1,159 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var Request = require('../../lib/request'); -var InvalidArgumentError = require('../../lib/errors/invalid-argument-error'); -var should = require('should'); - -/** - * Test `Request` integration. - */ - -describe('Request integration', function() { - describe('constructor()', function() { - it('should throw an error if `headers` is missing', function() { - try { - new Request({ body: {} }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `headers`'); - } - }); - - it('should throw an error if `method` is missing', function() { - try { - new Request({ body: {}, headers: {} }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `method`'); - } - }); - - it('should throw an error if `query` is missing', function() { - try { - new Request({ body: {}, headers: {}, method: {} }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `query`'); - } - }); - - it('should set the `body`', function() { - var request = new Request({ body: 'foo', headers: {}, method: {}, query: {} }); - - request.body.should.equal('foo'); - }); - - it('should set the `headers`', function() { - var request = new Request({ body: {}, headers: { foo: 'bar', QuX: 'biz' }, method: {}, query: {} }); - - request.headers.should.eql({ foo: 'bar', qux: 'biz' }); - }); - - it('should set the `method`', function() { - var request = new Request({ body: {}, headers: {}, method: 'biz', query: {} }); - - request.method.should.equal('biz'); - }); - - it('should set the `query`', function() { - var request = new Request({ body: {}, headers: {}, method: {}, query: 'baz' }); - - request.query.should.equal('baz'); - }); - }); - - describe('get()', function() { - it('should return `undefined` if the field does not exist', function() { - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - (undefined === request.get('content-type')).should.be.true; - }); - - it('should return the value if the field exists', function() { - var request = new Request({ - body: {}, - headers: { - 'content-type': 'text/html; charset=utf-8' - }, - method: {}, - query: {} - }); - - request.get('Content-Type').should.equal('text/html; charset=utf-8'); - }); - }); - - describe('is()', function() { - it('should accept an array of `types`', function() { - var request = new Request({ - body: {}, - headers: { - 'content-type': 'application/json', - 'transfer-encoding': 'chunked' - }, - method: {}, - query: {} - }); - - request.is(['html', 'json']).should.equal('json'); - }); - - it('should accept multiple `types` as arguments', function() { - var request = new Request({ - body: {}, - headers: { - 'content-type': 'application/json', - 'transfer-encoding': 'chunked' - }, - method: {}, - query: {} - }); - - request.is('html', 'json').should.equal('json'); - }); - - it('should return the first matching type', function() { - var request = new Request({ - body: {}, - headers: { - 'content-type': 'text/html; charset=utf-8', - 'transfer-encoding': 'chunked' - }, - method: {}, - query: {} - }); - - request.is('html').should.equal('html'); - }); - - it('should return `false` if none of the `types` match', function() { - var request = new Request({ - body: {}, - headers: { - 'content-type': 'text/html; charset=utf-8', - 'transfer-encoding': 'chunked' - }, - method: {}, - query: {} - }); - - request.is('json').should.be.false; - }); - - it('should return `false` if the request has no body', function() { - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - request.is('text/html').should.be.false; - }); - }); -}); diff --git a/test/integration/response-types/code-response-type.spec.ts b/test/integration/response-types/code-response-type.spec.ts new file mode 100755 index 000000000..9788ace3c --- /dev/null +++ b/test/integration/response-types/code-response-type.spec.ts @@ -0,0 +1,64 @@ +import * as should from 'should'; +import * as url from 'url'; +import { InvalidArgumentError } from '../../../lib/errors/invalid-argument-error'; +import { CodeResponseType } from '../../../lib/response-types/code-response-type'; + +/** + * Test `CodeResponseType` integration. + */ + +describe('CodeResponseType integration', () => { + describe('constructor()', () => { + it('should throw an error if `code` is missing', () => { + try { + new CodeResponseType(undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `code`'); + } + }); + + it('should set the `code`', () => { + const responseType = new CodeResponseType('foo' as any); + + responseType.code.should.equal('foo'); + }); + }); + + describe('buildRedirectUri()', () => { + it('should throw an error if the `redirectUri` is missing', () => { + const responseType = new CodeResponseType('foo' as any); + + try { + responseType.buildRedirectUri(undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `redirectUri`'); + } + }); + + it('should return the new redirect uri and set the `code` and `state` in the query', () => { + const responseType = new CodeResponseType('foo' as any); + const redirectUri = responseType.buildRedirectUri( + 'http://example.com/cb', + ); + + url.format(redirectUri).should.equal('http://example.com/cb?code=foo'); + }); + + it('should return the new redirect uri and append the `code` and `state` in the query', () => { + const responseType = new CodeResponseType('foo' as any); + const redirectUri = responseType.buildRedirectUri( + 'http://example.com/cb?foo=bar', + ); + + url + .format(redirectUri) + .should.equal('http://example.com/cb?foo=bar&code=foo'); + }); + }); +}); diff --git a/test/integration/response-types/code-response-type_test.js b/test/integration/response-types/code-response-type_test.js deleted file mode 100644 index 5461b62c4..000000000 --- a/test/integration/response-types/code-response-type_test.js +++ /dev/null @@ -1,64 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var CodeResponseType = require('../../../lib/response-types/code-response-type'); -var InvalidArgumentError = require('../../../lib/errors/invalid-argument-error'); -var should = require('should'); -var url = require('url'); - -/** - * Test `CodeResponseType` integration. - */ - -describe('CodeResponseType integration', function() { - describe('constructor()', function() { - it('should throw an error if `code` is missing', function() { - try { - new CodeResponseType(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `code`'); - } - }); - - it('should set the `code`', function() { - var responseType = new CodeResponseType('foo'); - - responseType.code.should.equal('foo'); - }); - }); - - describe('buildRedirectUri()', function() { - it('should throw an error if the `redirectUri` is missing', function() { - var responseType = new CodeResponseType('foo'); - - try { - responseType.buildRedirectUri(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `redirectUri`'); - } - }); - - it('should return the new redirect uri and set the `code` and `state` in the query', function() { - var responseType = new CodeResponseType('foo'); - var redirectUri = responseType.buildRedirectUri('http://example.com/cb'); - - url.format(redirectUri).should.equal('http://example.com/cb?code=foo'); - }); - - it('should return the new redirect uri and append the `code` and `state` in the query', function() { - var responseType = new CodeResponseType('foo'); - var redirectUri = responseType.buildRedirectUri('http://example.com/cb?foo=bar'); - - url.format(redirectUri).should.equal('http://example.com/cb?foo=bar&code=foo'); - }); - }); -}); diff --git a/test/integration/response.spec.ts b/test/integration/response.spec.ts new file mode 100755 index 000000000..dc73475af --- /dev/null +++ b/test/integration/response.spec.ts @@ -0,0 +1,75 @@ +import { Response } from '../../lib/response'; + +/** + * Test `Response` integration. + */ + +describe('Response integration', () => { + describe('constructor()', () => { + it('should set the `body`', () => { + const response = new Response({ body: 'foo', headers: {} }); + + response.body.should.equal('foo'); + }); + + it('should set the `headers`', () => { + const response = new Response({ + body: {}, + headers: { foo: 'bar', QuX: 'biz' }, + }); + + response.headers.should.eql({ foo: 'bar', qux: 'biz' }); + }); + + it('should set the `status` to 200', () => { + const response = new Response({ body: {}, headers: {} }); + + response.status.should.equal(200); + }); + }); + + describe('get()', () => { + it('should return `undefined` if the field does not exist', () => { + const response = new Response({ body: {}, headers: {} }); + + (response.get('content-type') === undefined).should.be.true(); + }); + + it('should return the value if the field exists', () => { + const response = new Response({ + body: {}, + headers: { 'content-type': 'text/html; charset=utf-8' }, + }); + + response.get('Content-Type').should.equal('text/html; charset=utf-8'); + }); + }); + + describe('redirect()', () => { + it('should set the location header to `url`', () => { + const response = new Response({ body: {}, headers: {} }); + + response.redirect('http://example.com'); + + response.get('Location').should.equal('http://example.com'); + }); + + it('should set the `status` to 302', () => { + const response = new Response({ body: {}, headers: {} }); + + response.redirect('http://example.com'); + + response.status.should.equal(302); + }); + }); + + describe('set()', () => { + it('should set the `field`', () => { + const response = new Response({ body: {}, headers: {} }); + + response.set('foo', 'bar'); + + response.headers.should.eql({ foo: 'bar' }); + }); + }); +}); diff --git a/test/integration/response_test.js b/test/integration/response_test.js deleted file mode 100644 index 1e1e0206a..000000000 --- a/test/integration/response_test.js +++ /dev/null @@ -1,75 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var Response = require('../../lib/response'); - -/** - * Test `Response` integration. - */ - -describe('Response integration', function() { - describe('constructor()', function() { - it('should set the `body`', function() { - var response = new Response({ body: 'foo', headers: {} }); - - response.body.should.equal('foo'); - }); - - it('should set the `headers`', function() { - var response = new Response({ body: {}, headers: { foo: 'bar', QuX: 'biz' } }); - - response.headers.should.eql({ foo: 'bar', qux: 'biz' }); - }); - - it('should set the `status` to 200', function() { - var response = new Response({ body: {}, headers: {} }); - - response.status.should.equal(200); - }); - }); - - describe('get()', function() { - it('should return `undefined` if the field does not exist', function() { - var response = new Response({ body: {}, headers: {} }); - - (undefined === response.get('content-type')).should.be.true; - }); - - it('should return the value if the field exists', function() { - var response = new Response({ body: {}, headers: { 'content-type': 'text/html; charset=utf-8' } }); - - response.get('Content-Type').should.equal('text/html; charset=utf-8'); - }); - }); - - describe('redirect()', function() { - it('should set the location header to `url`', function() { - var response = new Response({ body: {}, headers: {} }); - - response.redirect('http://example.com'); - - response.get('Location').should.equal('http://example.com'); - }); - - it('should set the `status` to 302', function() { - var response = new Response({ body: {}, headers: {} }); - - response.redirect('http://example.com'); - - response.status.should.equal(302); - }); - }); - - describe('set()', function() { - it('should set the `field`', function() { - var response = new Response({ body: {}, headers: {} }); - - response.set('foo', 'bar'); - - response.headers.should.eql({ foo: 'bar' }); - }); - }); -}); diff --git a/test/integration/server.spec.ts b/test/integration/server.spec.ts new file mode 100755 index 000000000..ff35ddddf --- /dev/null +++ b/test/integration/server.spec.ts @@ -0,0 +1,351 @@ +import * as should from 'should'; +import * as sinon from 'sinon'; +import { InvalidArgumentError } from '../../lib/errors/invalid-argument-error'; +import { AuthenticateHandler } from '../../lib/handlers/authenticate-handler'; +import { AuthorizeHandler } from '../../lib/handlers/authorize-handler'; +import { TokenHandler } from '../../lib/handlers/token-handler'; +import { Request } from '../../lib/request'; +import { Response } from '../../lib/response'; +import { OAuth2Server as Server } from '../../lib/server'; + +/** + * Test `Server` integration. + */ + +describe('Server integration', () => { + describe('constructor()', () => { + it('should throw an error if `model` is missing', () => { + try { + new Server({}); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `model`'); + } + }); + + it('should set the `model`', () => { + const model = {}; + const server = new Server({ model }); + + server.options.model.should.equal(model); + }); + }); + + describe('authenticate()', () => { + it('should set the default `options`', async () => { + const model = { + getAccessToken() { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + }; + const server = new Server({ model }); + const request = new Request({ + body: {}, + headers: { Authorization: 'Bearer foo' }, + method: {}, + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + try { + const stub = sinon + .stub(AuthenticateHandler.prototype, 'handle') + .returnsThis(); + const token = await server.authenticate(request, response); + token.addAcceptedScopesHeader.should.be.true(); + token.addAuthorizedScopesHeader.should.be.true(); + token.allowBearerTokensInQueryString.should.be.false(); + stub.restore(); + } catch (error) { + should.fail('should.fail', ''); + } + }); + + it('should return a promise', () => { + const model = { + async getAccessToken(token) { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + }; + const server = new Server({ model }); + const request = new Request({ + body: {}, + headers: { Authorization: 'Bearer foo' }, + method: {}, + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + const handler = server.authenticate(request, response); + + handler.should.be.an.instanceOf(Promise); + }); + + /* it('should support callbacks', next => { + const model = { + getAccessToken() { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + }; + const server = new Server({ model }); + const request = new Request({ + body: {}, + headers: { Authorization: 'Bearer foo' }, + method: {}, + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + // server.authenticate(request, response, null, next); + }); */ + }); + + describe('authorize()', () => { + it('should set the default `options`', async () => { + const model = { + async getAccessToken() { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + async getClient() { + return { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + }, + async saveAuthorizationCode() { + return { authorizationCode: 123 }; + }, + }; + const server = new Server({ model }); + const request = new Request({ + body: { + client_id: 1234, + client_secret: 'secret', + response_type: 'code', + }, + headers: { Authorization: 'Bearer foo' }, + method: {}, + query: { state: 'foobar' }, + }); + const response = new Response({ body: {}, headers: {} }); + try { + const stub = sinon + .stub(AuthorizeHandler.prototype, 'handle') + .returnsThis(); + const code = await server.authorize(request, response); + code.allowEmptyState.should.be.false(); + code.authorizationCodeLifetime.should.be.equal(300); + stub.restore(); + } catch (error) { + should.fail('should.fail', ''); + } + }); + + it('should return a promise', () => { + const model = { + getAccessToken() { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + getClient() { + return { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + }, + saveAuthorizationCode() { + return { authorizationCode: 123 }; + }, + }; + const server = new Server({ model }); + const request = new Request({ + body: { + client_id: 1234, + client_secret: 'secret', + response_type: 'code', + }, + headers: { Authorization: 'Bearer foo' }, + method: {}, + query: { state: 'foobar' }, + }); + + const response = new Response({ body: {}, headers: {} }); + try { + const handler = server.authorize(request, response); + handler.should.be.an.instanceOf(Promise); + } catch (error) { + should.fail('should.fail', ''); + } + }); + + /* it('should support callbacks', next => { + const model = { + getAccessToken() { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + getClient() { + return { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + }, + saveAuthorizationCode() { + return { authorizationCode: 123 }; + }, + }; + const server = new Server({ model }); + const request = new Request({ + body: { + client_id: 1234, + client_secret: 'secret', + response_type: 'code', + }, + headers: { Authorization: 'Bearer foo' }, + method: {}, + query: { state: 'foobar' }, + }); + const response = new Response({ body: {}, headers: {} }); + + // tslint:disable-next-line: no-floating-promises + // server.authorize(request, response, undefined, next); + }); */ + }); + + describe('token()', () => { + it('should set the default `options`', async () => { + const model = { + async getClient() { + return { grants: ['password'] }; + }, + async getUser() { + return {}; + }, + async saveToken() { + return { accessToken: 1234, client: {}, user: {} }; + }, + async validateScope() { + return 'foo'; + }, + }; + const server = new Server({ model }); + const request = new Request({ + body: { + client_id: 1234, + client_secret: 'secret', + grant_type: 'password', + username: 'foo', + password: 'pass', + scope: 'foo', + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + const stub = sinon.stub(TokenHandler.prototype, 'handle').returnsThis(); + // try { + const token = await server.token(request, response); + token.accessTokenLifetime.should.equal(3600); + token.refreshTokenLifetime.should.equal(1209600); + stub.restore(); + // } catch (error) { + // should.fail('should.fail', ''); + // } + }); + + it('should return a promise', () => { + const model = { + async getClient() { + return { grants: ['password'] }; + }, + async getUser() { + return {}; + }, + async saveToken() { + return { accessToken: 1234, client: {}, user: {} }; + }, + }; + const server = new Server({ model }); + const request = new Request({ + body: { + client_id: 1234, + client_secret: 'secret', + grant_type: 'password', + username: 'foo', + password: 'pass', + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + // try { + const handler = server.token(request, response); + + handler.should.be.an.instanceOf(Promise); + // } catch (error) { + // should.fail('should.fail', ''); + // } + }); + + /* it('should support callbacks', next => { + const model = { + async () { + return { grants: ['password'] }; + }, + getUser() { + return {}; + }, + saveToken() { + return { accessToken: 1234, client: {}, user: {} }; + }, + validateScope() { + return 'foo'; + }, + }; + const server = new Server({ model }); + const request = new Request({ + body: { + client_id: 1234, + client_secret: 'secret', + grant_type: 'password', + username: 'foo', + password: 'pass', + scope: 'foo', + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + // server.token(request, response, null, next); + }); */ + }); +}); diff --git a/test/integration/server_test.js b/test/integration/server_test.js deleted file mode 100644 index 2d3aa7845..000000000 --- a/test/integration/server_test.js +++ /dev/null @@ -1,238 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var InvalidArgumentError = require('../../lib/errors/invalid-argument-error'); -var Promise = require('bluebird'); -var Request = require('../../lib/request'); -var Response = require('../../lib/response'); -var Server = require('../../lib/server'); -var should = require('should'); - -/** - * Test `Server` integration. - */ - -describe('Server integration', function() { - describe('constructor()', function() { - it('should throw an error if `model` is missing', function() { - try { - new Server({}); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `model`'); - } - }); - - it('should set the `model`', function() { - var model = {}; - var server = new Server({ model: model }); - - server.options.model.should.equal(model); - }); - }); - - describe('authenticate()', function() { - it('should set the default `options`', function() { - var model = { - getAccessToken: function() { - return { - user: {}, - accessTokenExpiresAt: new Date(new Date().getTime() + 10000) - }; - } - }; - var server = new Server({ model: model }); - var request = new Request({ body: {}, headers: { 'Authorization': 'Bearer foo' }, method: {}, query: {} }); - var response = new Response({ body: {}, headers: {} }); - - return server.authenticate(request, response) - .then(function() { - this.addAcceptedScopesHeader.should.be.true; - this.addAuthorizedScopesHeader.should.be.true; - this.allowBearerTokensInQueryString.should.be.false; - }) - .catch(should.fail); - }); - - it('should return a promise', function() { - var model = { - getAccessToken: function(token, callback) { - callback(null, { - user: {}, - accessTokenExpiresAt: new Date(new Date().getTime() + 10000) - }); - } - }; - var server = new Server({ model: model }); - var request = new Request({ body: {}, headers: { 'Authorization': 'Bearer foo' }, method: {}, query: {} }); - var response = new Response({ body: {}, headers: {} }); - var handler = server.authenticate(request, response); - - handler.should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function(next) { - var model = { - getAccessToken: function() { - return { - user: {}, - accessTokenExpiresAt: new Date(new Date().getTime() + 10000) - }; - } - }; - var server = new Server({ model: model }); - var request = new Request({ body: {}, headers: { 'Authorization': 'Bearer foo' }, method: {}, query: {} }); - var response = new Response({ body: {}, headers: {} }); - - server.authenticate(request, response, null, next); - }); - }); - - describe('authorize()', function() { - it('should set the default `options`', function() { - var model = { - getAccessToken: function() { - return { - user: {}, - accessTokenExpiresAt: new Date(new Date().getTime() + 10000) - }; - }, - getClient: function() { - return { grants: ['authorization_code'], redirectUris: ['http://example.com/cb'] }; - }, - saveAuthorizationCode: function() { - return { authorizationCode: 123 }; - } - }; - var server = new Server({ model: model }); - var request = new Request({ body: { client_id: 1234, client_secret: 'secret', response_type: 'code' }, headers: { 'Authorization': 'Bearer foo' }, method: {}, query: { state: 'foobar' } }); - var response = new Response({ body: {}, headers: {} }); - - return server.authorize(request, response) - .then(function() { - this.allowEmptyState.should.be.false; - this.authorizationCodeLifetime.should.equal(300); - }) - .catch(should.fail); - }); - - it('should return a promise', function() { - var model = { - getAccessToken: function() { - return { - user: {}, - accessTokenExpiresAt: new Date(new Date().getTime() + 10000) - }; - }, - getClient: function() { - return { grants: ['authorization_code'], redirectUris: ['http://example.com/cb'] }; - }, - saveAuthorizationCode: function() { - return { authorizationCode: 123 }; - } - }; - var server = new Server({ model: model }); - var request = new Request({ body: { client_id: 1234, client_secret: 'secret', response_type: 'code' }, headers: { 'Authorization': 'Bearer foo' }, method: {}, query: { state: 'foobar' } }); - var response = new Response({ body: {}, headers: {} }); - var handler = server.authorize(request, response); - - handler.should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function(next) { - var model = { - getAccessToken: function() { - return { - user: {}, - accessTokenExpiresAt: new Date(new Date().getTime() + 10000) - }; - }, - getClient: function() { - return { grants: ['authorization_code'], redirectUris: ['http://example.com/cb'] }; - }, - saveAuthorizationCode: function() { - return { authorizationCode: 123 }; - } - }; - var server = new Server({ model: model }); - var request = new Request({ body: { client_id: 1234, client_secret: 'secret', response_type: 'code' }, headers: { 'Authorization': 'Bearer foo' }, method: {}, query: { state: 'foobar' } }); - var response = new Response({ body: {}, headers: {} }); - - server.authorize(request, response, null, next); - }); - }); - - describe('token()', function() { - it('should set the default `options`', function() { - var model = { - getClient: function() { - return { grants: ['password'] }; - }, - getUser: function() { - return {}; - }, - saveToken: function() { - return { accessToken: 1234, client: {}, user: {} }; - }, - validateScope: function() { return 'foo'; } - }; - var server = new Server({ model: model }); - var request = new Request({ body: { client_id: 1234, client_secret: 'secret', grant_type: 'password', username: 'foo', password: 'pass', scope: 'foo' }, headers: { 'content-type': 'application/x-www-form-urlencoded', 'transfer-encoding': 'chunked' }, method: 'POST', query: {} }); - var response = new Response({ body: {}, headers: {} }); - - return server.token(request, response) - .then(function() { - this.accessTokenLifetime.should.equal(3600); - this.refreshTokenLifetime.should.equal(1209600); - }) - .catch(should.fail); - }); - - it('should return a promise', function() { - var model = { - getClient: function() { - return { grants: ['password'] }; - }, - getUser: function() { - return {}; - }, - saveToken: function() { - return { accessToken: 1234, client: {}, user: {} }; - } - }; - var server = new Server({ model: model }); - var request = new Request({ body: { client_id: 1234, client_secret: 'secret', grant_type: 'password', username: 'foo', password: 'pass' }, headers: { 'content-type': 'application/x-www-form-urlencoded', 'transfer-encoding': 'chunked' }, method: 'POST', query: {} }); - var response = new Response({ body: {}, headers: {} }); - var handler = server.token(request, response); - - handler.should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function(next) { - var model = { - getClient: function() { - return { grants: ['password'] }; - }, - getUser: function() { - return {}; - }, - saveToken: function() { - return { accessToken: 1234, client: {}, user: {} }; - }, - validateScope: function() { - return 'foo'; - } - }; - var server = new Server({ model: model }); - var request = new Request({ body: { client_id: 1234, client_secret: 'secret', grant_type: 'password', username: 'foo', password: 'pass', scope: 'foo' }, headers: { 'content-type': 'application/x-www-form-urlencoded', 'transfer-encoding': 'chunked' }, method: 'POST', query: {} }); - var response = new Response({ body: {}, headers: {} }); - - server.token(request, response, null, next); - }); - }); -}); diff --git a/test/integration/token-types/bearer-token-type.spec.ts b/test/integration/token-types/bearer-token-type.spec.ts new file mode 100755 index 000000000..25e70d2ff --- /dev/null +++ b/test/integration/token-types/bearer-token-type.spec.ts @@ -0,0 +1,135 @@ +import * as should from 'should'; +import { InvalidArgumentError } from '../../../lib/errors/invalid-argument-error'; +import { BearerTokenType } from '../../../lib/token-types/bearer-token-type'; + +/** + * Test `BearerTokenType` integration. + */ + +describe('BearerTokenType integration', () => { + describe('constructor()', () => { + it('should throw an error if `accessToken` is missing', () => { + try { + new BearerTokenType( + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `accessToken`'); + } + }); + + it('should set the `accessToken`', () => { + const responseType = new BearerTokenType( + 'foo', + 'bar' as any, + undefined, + undefined, + undefined, + ); + + responseType.accessToken.should.equal('foo'); + }); + + it('should set the `accessTokenLifetime`', () => { + const responseType = new BearerTokenType( + 'foo', + 'bar' as any, + undefined, + undefined, + undefined, + ); + + responseType.accessTokenLifetime.should.equal('bar'); + }); + + it('should set the `refreshToken`', () => { + const responseType = new BearerTokenType( + 'foo', + 'bar' as any, + 'biz', + undefined, + undefined, + ); + + responseType.refreshToken.should.equal('biz'); + }); + }); + + describe('valueOf()', () => { + it('should return the value representation', () => { + const responseType = new BearerTokenType( + 'foo', + 'bar' as any, + undefined, + undefined, + undefined, + ); + const value = responseType.valueOf(); + + value.should.eql({ + access_token: 'foo', + expires_in: 'bar', + token_type: 'Bearer', + }); + }); + + it('should not include the `expires_in` if not given', () => { + const responseType = new BearerTokenType( + 'foo', + undefined, + undefined, + undefined, + undefined, + ); + const value = responseType.valueOf(); + + value.should.eql({ + access_token: 'foo', + token_type: 'Bearer', + }); + }); + + it('should set `refresh_token` if `refreshToken` is defined', () => { + const responseType = new BearerTokenType( + 'foo', + 'bar' as any, + 'biz', + undefined, + undefined, + ); + const value = responseType.valueOf(); + + value.should.eql({ + access_token: 'foo', + expires_in: 'bar', + refresh_token: 'biz', + token_type: 'Bearer', + }); + }); + + it('should set `expires_in` if `accessTokenLifetime` is defined', () => { + const responseType = new BearerTokenType( + 'foo', + 'bar' as any, + 'biz', + undefined, + undefined, + ); + const value = responseType.valueOf(); + + value.should.eql({ + access_token: 'foo', + expires_in: 'bar', + refresh_token: 'biz', + token_type: 'Bearer', + }); + }); + }); +}); diff --git a/test/integration/token-types/bearer-token-type_test.js b/test/integration/token-types/bearer-token-type_test.js deleted file mode 100644 index 3c1ef6bdd..000000000 --- a/test/integration/token-types/bearer-token-type_test.js +++ /dev/null @@ -1,93 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var BearerTokenType = require('../../../lib/token-types/bearer-token-type'); -var InvalidArgumentError = require('../../../lib/errors/invalid-argument-error'); -var should = require('should'); - -/** - * Test `BearerTokenType` integration. - */ - -describe('BearerTokenType integration', function() { - describe('constructor()', function() { - it('should throw an error if `accessToken` is missing', function() { - try { - new BearerTokenType(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `accessToken`'); - } - }); - - it('should set the `accessToken`', function() { - var responseType = new BearerTokenType('foo', 'bar'); - - responseType.accessToken.should.equal('foo'); - }); - - it('should set the `accessTokenLifetime`', function() { - var responseType = new BearerTokenType('foo', 'bar'); - - responseType.accessTokenLifetime.should.equal('bar'); - }); - - it('should set the `refreshToken`', function() { - var responseType = new BearerTokenType('foo', 'bar', 'biz'); - - responseType.refreshToken.should.equal('biz'); - }); - }); - - describe('valueOf()', function() { - it('should return the value representation', function() { - var responseType = new BearerTokenType('foo', 'bar'); - var value = responseType.valueOf(); - - value.should.eql({ - access_token: 'foo', - expires_in: 'bar', - token_type: 'Bearer' - }); - }); - - it('should not include the `expires_in` if not given', function() { - var responseType = new BearerTokenType('foo'); - var value = responseType.valueOf(); - - value.should.eql({ - access_token: 'foo', - token_type: 'Bearer' - }); - }); - - it('should set `refresh_token` if `refreshToken` is defined', function() { - var responseType = new BearerTokenType('foo', 'bar', 'biz'); - var value = responseType.valueOf(); - - value.should.eql({ - access_token: 'foo', - expires_in: 'bar', - refresh_token: 'biz', - token_type: 'Bearer' - }); - }); - - it('should set `expires_in` if `accessTokenLifetime` is defined', function() { - var responseType = new BearerTokenType('foo', 'bar', 'biz'); - var value = responseType.valueOf(); - - value.should.eql({ - access_token: 'foo', - expires_in: 'bar', - refresh_token: 'biz', - token_type: 'Bearer' - }); - }); - }); -}); diff --git a/test/integration/utils/token-util.spec.ts b/test/integration/utils/token-util.spec.ts new file mode 100755 index 000000000..9a7ea1747 --- /dev/null +++ b/test/integration/utils/token-util.spec.ts @@ -0,0 +1,19 @@ +import * as should from 'should'; +import * as TokenUtil from '../../../lib/utils/token-util'; + +/** + * Test `TokenUtil` integration. + */ + +describe('TokenUtil integration', () => { + describe('generateRandomToken()', () => { + it('should return a sha-1 token', async () => { + try { + const token: any = await TokenUtil.GenerateRandomToken(); + token.should.be.a.sha1(); + } catch (error) { + should.fail('should.fail', ''); + } + }); + }); +}); diff --git a/test/integration/utils/token-util_test.js b/test/integration/utils/token-util_test.js deleted file mode 100644 index 3fbca3f65..000000000 --- a/test/integration/utils/token-util_test.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var TokenUtil = require('../../../lib/utils/token-util'); -var should = require('should'); - -/** - * Test `TokenUtil` integration. - */ - -describe('TokenUtil integration', function() { - describe('generateRandomToken()', function() { - it('should return a sha-1 token', function() { - return TokenUtil.generateRandomToken() - .then(function(token) { - token.should.be.a.sha1; - }) - .catch(should.fail); - }); - }); -}); diff --git a/test/mocha.opts b/test/mocha.opts deleted file mode 100644 index 00ecb38eb..000000000 --- a/test/mocha.opts +++ /dev/null @@ -1,4 +0,0 @@ ---require should ---require test/assertions ---ui bdd ---reporter spec diff --git a/test/tslint.json b/test/tslint.json new file mode 100755 index 000000000..d0e928687 --- /dev/null +++ b/test/tslint.json @@ -0,0 +1,29 @@ +{ + // "defaultSeverity": "error", + "extends": ["tslint:recommended", "tslint:all", "tslint:latest"], + "jsRules": { + "no-unused-expression": true + }, + "rules": { + "file-name-casing": [true, "kebab-case"], + "no-unused-expression": false, + "semicolon": [true, "always", "ignore-bound-class-methods"], + "completed-docs": false, + "quotemark": [true, "single"], + "max-file-line-count": [false], + "member-access": [false], + "no-unsafe-any": false, + "promise-function-async": false, + "ordered-imports": [false], + "no-empty": [false], + "newline-per-chained-call": [false], + "max-line-length": [true, 120], + "no-magic-numbers": [false], + "member-ordering": [false], + "interface-name": [false], + "arrow-parens": false, + "no-implicit-dependencies": false, + "object-literal-sort-keys": false + }, + "rulesDirectory": [] +} diff --git a/test/unit/grant-types/abstract-grant-type.spec.ts b/test/unit/grant-types/abstract-grant-type.spec.ts new file mode 100755 index 000000000..1e6893c20 --- /dev/null +++ b/test/unit/grant-types/abstract-grant-type.spec.ts @@ -0,0 +1,53 @@ +import * as should from 'should'; +import * as sinon from 'sinon'; +import { AbstractGrantType } from '../../../lib/grant-types/abstract-grant-type'; + +/** + * Test `AbstractGrantType`. + */ + +describe('AbstractGrantType', () => { + describe('generateAccessToken()', () => { + it('should call `model.generateAccessToken()`', async () => { + const model = { + generateAccessToken: sinon + .stub() + .returns({ client: {}, expiresAt: new Date(), user: {} }), + }; + const handler = new AbstractGrantType({ + accessTokenLifetime: 120, + model, + }); + try { + await handler.generateAccessToken(); + model.generateAccessToken.callCount.should.equal(1); + model.generateAccessToken.firstCall.thisValue.should.equal(model); + } catch (error) { + should.fail('should.fail', ''); + } + }); + }); + + describe('generateRefreshToken()', () => { + it('should call `model.generateRefreshToken()`', async () => { + const model = { + generateRefreshToken: sinon.stub().returns({ + client: {}, + expiresAt: new Date(new Date().getTime() / 2), + user: {}, + }), + }; + const handler = new AbstractGrantType({ + accessTokenLifetime: 120, + model, + }); + try { + await handler.generateRefreshToken(); + model.generateRefreshToken.callCount.should.equal(1); + model.generateRefreshToken.firstCall.thisValue.should.equal(model); + } catch (error) { + should.fail('should.fail', ''); + } + }); + }); +}); diff --git a/test/unit/grant-types/abstract-grant-type_test.js b/test/unit/grant-types/abstract-grant-type_test.js deleted file mode 100644 index 528ca4041..000000000 --- a/test/unit/grant-types/abstract-grant-type_test.js +++ /dev/null @@ -1,47 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var AbstractGrantType = require('../../../lib/grant-types/abstract-grant-type'); -var sinon = require('sinon'); -var should = require('should'); - -/** - * Test `AbstractGrantType`. - */ - -describe('AbstractGrantType', function() { - describe('generateAccessToken()', function() { - it('should call `model.generateAccessToken()`', function() { - var model = { - generateAccessToken: sinon.stub().returns({ client: {}, expiresAt: new Date(), user: {} }) - }; - var handler = new AbstractGrantType({ accessTokenLifetime: 120, model: model }); - - return handler.generateAccessToken() - .then(function() { - model.generateAccessToken.callCount.should.equal(1); - model.generateAccessToken.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); - - describe('generateRefreshToken()', function() { - it('should call `model.generateRefreshToken()`', function() { - var model = { - generateRefreshToken: sinon.stub().returns({ client: {}, expiresAt: new Date(new Date() / 2), user: {} }) - }; - var handler = new AbstractGrantType({ accessTokenLifetime: 120, model: model }); - - return handler.generateRefreshToken() - .then(function() { - model.generateRefreshToken.callCount.should.equal(1); - model.generateRefreshToken.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); -}); diff --git a/test/unit/grant-types/authorization-code-grant-type.spec.ts b/test/unit/grant-types/authorization-code-grant-type.spec.ts new file mode 100755 index 000000000..b775a6603 --- /dev/null +++ b/test/unit/grant-types/authorization-code-grant-type.spec.ts @@ -0,0 +1,121 @@ +import * as should from 'should'; +import * as sinon from 'sinon'; +import { AuthorizationCodeGrantType } from '../../../lib/grant-types/authorization-code-grant-type'; +import { Request } from '../../../lib/request'; + +/** + * Test `AuthorizationCodeGrantType`. + */ + +describe('AuthorizationCodeGrantType', () => { + describe('getAuthorizationCode()', () => { + it('should call `model.getAuthorizationCode()`', async () => { + const model = { + getAuthorizationCode: sinon.stub().returns({ + authorizationCode: 12345, + client: {}, + expiresAt: new Date(new Date().getTime() * 2), + user: {}, + }), + revokeAuthorizationCode() {}, + saveToken() {}, + }; + const handler = new AuthorizationCodeGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: {}, + query: {}, + }); + const client = {}; + try { + await handler.getAuthorizationCode(request, client); + + model.getAuthorizationCode.callCount.should.equal(1); + model.getAuthorizationCode.firstCall.args.should.have.length(1); + model.getAuthorizationCode.firstCall.args[0].should.equal(12345); + model.getAuthorizationCode.firstCall.thisValue.should.equal(model); + } catch (error) { + should.fail('should.fail', ''); + } + }); + }); + + describe('revokeAuthorizationCode()', () => { + it('should call `model.revokeAuthorizationCode()`', async () => { + const model = { + getAuthorizationCode() {}, + revokeAuthorizationCode: sinon.stub().returns(true), + saveToken() {}, + }; + const handler = new AuthorizationCodeGrantType({ + accessTokenLifetime: 120, + model, + }); + const authorizationCode = {}; + try { + await handler.revokeAuthorizationCode(authorizationCode); + + model.revokeAuthorizationCode.callCount.should.equal(1); + model.revokeAuthorizationCode.firstCall.args.should.have.length(1); + model.revokeAuthorizationCode.firstCall.args[0].should.equal( + authorizationCode, + ); + model.revokeAuthorizationCode.firstCall.thisValue.should.equal(model); + } catch (error) { + should.fail('should.fail', ''); + } + }); + }); + + describe('saveToken()', () => { + it('should call `model.saveToken()`', async () => { + const client: any = {}; + const user = {}; + const model = { + getAuthorizationCode() {}, + revokeAuthorizationCode() {}, + saveToken: sinon.stub().returns(true), + }; + const handler = new AuthorizationCodeGrantType({ + accessTokenLifetime: 120, + model, + }); + + sinon.stub(handler, 'validateScope').returns('foobiz' as any); + sinon + .stub(handler, 'generateAccessToken') + .returns(Promise.resolve('foo')); + sinon + .stub(handler, 'generateRefreshToken') + .returns(Promise.resolve('bar')); + sinon + .stub(handler, 'getAccessTokenExpiresAt') + .returns(Promise.resolve('biz') as any); + sinon + .stub(handler, 'getRefreshTokenExpiresAt') + .returns(Promise.resolve('baz') as any); + try { + await handler.saveToken(user, client, 'foobar', 'foobiz'); + model.saveToken.callCount.should.equal(1); + model.saveToken.firstCall.args.should.have.length(3); + model.saveToken.firstCall.args[0].should.eql({ + accessToken: 'foo', + authorizationCode: 'foobar', + accessTokenExpiresAt: 'biz', + refreshToken: 'bar', + refreshTokenExpiresAt: 'baz', + scope: 'foobiz', + }); + model.saveToken.firstCall.args[1].should.equal(client); + model.saveToken.firstCall.args[2].should.equal(user); + model.saveToken.firstCall.thisValue.should.equal(model); + } catch (error) { + should.fail('should.fail', ''); + } + }); + }); +}); diff --git a/test/unit/grant-types/authorization-code-grant-type_test.js b/test/unit/grant-types/authorization-code-grant-type_test.js deleted file mode 100644 index 480416e68..000000000 --- a/test/unit/grant-types/authorization-code-grant-type_test.js +++ /dev/null @@ -1,90 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var AuthorizationCodeGrantType = require('../../../lib/grant-types/authorization-code-grant-type'); -var Promise = require('bluebird'); -var Request = require('../../../lib/request'); -var sinon = require('sinon'); -var should = require('should'); - -/** - * Test `AuthorizationCodeGrantType`. - */ - -describe('AuthorizationCodeGrantType', function() { - describe('getAuthorizationCode()', function() { - it('should call `model.getAuthorizationCode()`', function() { - var model = { - getAuthorizationCode: sinon.stub().returns({ authorizationCode: 12345, client: {}, expiresAt: new Date(new Date() * 2), user: {} }), - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var handler = new AuthorizationCodeGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - var client = {}; - - return handler.getAuthorizationCode(request, client) - .then(function() { - model.getAuthorizationCode.callCount.should.equal(1); - model.getAuthorizationCode.firstCall.args.should.have.length(1); - model.getAuthorizationCode.firstCall.args[0].should.equal(12345); - model.getAuthorizationCode.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); - - describe('revokeAuthorizationCode()', function() { - it('should call `model.revokeAuthorizationCode()`', function() { - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: sinon.stub().returns(true), - saveToken: function() {} - }; - var handler = new AuthorizationCodeGrantType({ accessTokenLifetime: 120, model: model }); - var authorizationCode = {}; - - return handler.revokeAuthorizationCode(authorizationCode) - .then(function() { - model.revokeAuthorizationCode.callCount.should.equal(1); - model.revokeAuthorizationCode.firstCall.args.should.have.length(1); - model.revokeAuthorizationCode.firstCall.args[0].should.equal(authorizationCode); - model.revokeAuthorizationCode.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); - - describe('saveToken()', function() { - it('should call `model.saveToken()`', function() { - var client = {}; - var user = {}; - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function() {}, - saveToken: sinon.stub().returns(true) - }; - var handler = new AuthorizationCodeGrantType({ accessTokenLifetime: 120, model: model }); - - sinon.stub(handler, 'validateScope').returns('foobiz'); - sinon.stub(handler, 'generateAccessToken').returns(Promise.resolve('foo')); - sinon.stub(handler, 'generateRefreshToken').returns(Promise.resolve('bar')); - sinon.stub(handler, 'getAccessTokenExpiresAt').returns(Promise.resolve('biz')); - sinon.stub(handler, 'getRefreshTokenExpiresAt').returns(Promise.resolve('baz')); - - return handler.saveToken(user, client, 'foobar', 'foobiz') - .then(function() { - model.saveToken.callCount.should.equal(1); - model.saveToken.firstCall.args.should.have.length(3); - model.saveToken.firstCall.args[0].should.eql({ accessToken: 'foo', authorizationCode: 'foobar', accessTokenExpiresAt: 'biz', refreshToken: 'bar', refreshTokenExpiresAt: 'baz', scope: 'foobiz' }); - model.saveToken.firstCall.args[1].should.equal(client); - model.saveToken.firstCall.args[2].should.equal(user); - model.saveToken.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); -}); diff --git a/test/unit/grant-types/client-credentials-grant-type.spec.ts b/test/unit/grant-types/client-credentials-grant-type.spec.ts new file mode 100755 index 000000000..b0585d609 --- /dev/null +++ b/test/unit/grant-types/client-credentials-grant-type.spec.ts @@ -0,0 +1,66 @@ +import * as should from 'should'; +import * as sinon from 'sinon'; +import { ClientCredentialsGrantType } from '../../../lib/grant-types/client-credentials-grant-type'; + +/** + * Test `ClientCredentialsGrantType`. + */ + +describe('ClientCredentialsGrantType', () => { + describe('getUserFromClient()', () => { + it('should call `model.getUserFromClient()`', async () => { + const model = { + getUserFromClient: sinon.stub().returns(true), + saveToken() {}, + }; + const handler = new ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + const client: any = {}; + try { + await handler.getUserFromClient(client); + model.getUserFromClient.callCount.should.equal(1); + model.getUserFromClient.firstCall.args.should.have.length(1); + model.getUserFromClient.firstCall.args[0].should.equal(client); + model.getUserFromClient.firstCall.thisValue.should.equal(model); + } catch (error) { + should.fail('should.fail', ''); + } + }); + }); + + describe('saveToken()', () => { + it('should call `model.saveToken()`', async () => { + const client: any = {}; + const user = {}; + const model = { + getUserFromClient() {}, + saveToken: sinon.stub().returns(true), + }; + const handler = new ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + + sinon.stub(handler, 'validateScope').returns('foobar' as any); + sinon.stub(handler, 'generateAccessToken').returns('foo' as any); + sinon.stub(handler, 'getAccessTokenExpiresAt').returns('biz' as any); + try { + await handler.saveToken(user, client, 'foobar'); + model.saveToken.callCount.should.equal(1); + model.saveToken.firstCall.args.should.have.length(3); + model.saveToken.firstCall.args[0].should.eql({ + accessToken: 'foo', + accessTokenExpiresAt: 'biz', + scope: 'foobar', + }); + model.saveToken.firstCall.args[1].should.equal(client); + model.saveToken.firstCall.args[2].should.equal(user); + model.saveToken.firstCall.thisValue.should.equal(model); + } catch (error) { + should.fail('should.fail', ''); + } + }); + }); +}); diff --git a/test/unit/grant-types/client-credentials-grant-type_test.js b/test/unit/grant-types/client-credentials-grant-type_test.js deleted file mode 100644 index fe1fc4840..000000000 --- a/test/unit/grant-types/client-credentials-grant-type_test.js +++ /dev/null @@ -1,62 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var ClientCredentialsGrantType = require('../../../lib/grant-types/client-credentials-grant-type'); -var sinon = require('sinon'); -var should = require('should'); - -/** - * Test `ClientCredentialsGrantType`. - */ - -describe('ClientCredentialsGrantType', function() { - describe('getUserFromClient()', function() { - it('should call `model.getUserFromClient()`', function() { - var model = { - getUserFromClient: sinon.stub().returns(true), - saveToken: function() {} - }; - var handler = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); - var client = {}; - - return handler.getUserFromClient(client) - .then(function() { - model.getUserFromClient.callCount.should.equal(1); - model.getUserFromClient.firstCall.args.should.have.length(1); - model.getUserFromClient.firstCall.args[0].should.equal(client); - model.getUserFromClient.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); - - describe('saveToken()', function() { - it('should call `model.saveToken()`', function() { - var client = {}; - var user = {}; - var model = { - getUserFromClient: function() {}, - saveToken: sinon.stub().returns(true) - }; - var handler = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); - - sinon.stub(handler, 'validateScope').returns('foobar'); - sinon.stub(handler, 'generateAccessToken').returns('foo'); - sinon.stub(handler, 'getAccessTokenExpiresAt').returns('biz'); - - return handler.saveToken(user, client, 'foobar') - .then(function() { - model.saveToken.callCount.should.equal(1); - model.saveToken.firstCall.args.should.have.length(3); - model.saveToken.firstCall.args[0].should.eql({ accessToken: 'foo', accessTokenExpiresAt: 'biz', scope: 'foobar' }); - model.saveToken.firstCall.args[1].should.equal(client); - model.saveToken.firstCall.args[2].should.equal(user); - model.saveToken.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); -}); diff --git a/test/unit/grant-types/password-grant-type.spec.ts b/test/unit/grant-types/password-grant-type.spec.ts new file mode 100755 index 000000000..7ef7b24a5 --- /dev/null +++ b/test/unit/grant-types/password-grant-type.spec.ts @@ -0,0 +1,78 @@ +import * as should from 'should'; +import * as sinon from 'sinon'; +import { PasswordGrantType } from '../../../lib/grant-types/password-grant-type'; +import { Request } from '../../../lib/request'; + +/** + * Test `PasswordGrantType`. + */ + +describe('PasswordGrantType', () => { + describe('getUser()', () => { + it('should call `model.getUser()`', async () => { + const model = { + getUser: sinon.stub().returns(true), + saveToken() {}, + }; + const handler = new PasswordGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: { username: 'foo', password: 'bar' }, + headers: {}, + method: {}, + query: {}, + }); + try { + await handler.getUser(request); + model.getUser.callCount.should.equal(1); + model.getUser.firstCall.args.should.have.length(2); + model.getUser.firstCall.args[0].should.equal('foo'); + model.getUser.firstCall.args[1].should.equal('bar'); + model.getUser.firstCall.thisValue.should.equal(model); + } catch (error) { + should.fail('should.fail', ''); + } + }); + }); + + describe('saveToken()', () => { + it('should call `model.saveToken()`', async () => { + const client: any = {}; + const user = {}; + const model = { + getUser() {}, + saveToken: sinon.stub().returns(true), + }; + const handler = new PasswordGrantType({ + accessTokenLifetime: 120, + model, + }); + + sinon.stub(handler, 'validateScope').returns('foobar' as any); + sinon.stub(handler, 'generateAccessToken').returns('foo' as any); + sinon.stub(handler, 'generateRefreshToken').returns('bar' as any); + sinon.stub(handler, 'getAccessTokenExpiresAt').returns('biz' as any); + sinon.stub(handler, 'getRefreshTokenExpiresAt').returns('baz' as any); + try { + await handler.saveToken(user, client, 'foobar'); + + model.saveToken.callCount.should.equal(1); + model.saveToken.firstCall.args.should.have.length(3); + model.saveToken.firstCall.args[0].should.eql({ + accessToken: 'foo', + accessTokenExpiresAt: 'biz', + refreshToken: 'bar', + refreshTokenExpiresAt: 'baz', + scope: 'foobar', + }); + model.saveToken.firstCall.args[1].should.equal(client); + model.saveToken.firstCall.args[2].should.equal(user); + model.saveToken.firstCall.thisValue.should.equal(model); + } catch (error) { + should.fail('should.fail', ''); + } + }); + }); +}); diff --git a/test/unit/grant-types/password-grant-type_test.js b/test/unit/grant-types/password-grant-type_test.js deleted file mode 100644 index 8e3bfc84e..000000000 --- a/test/unit/grant-types/password-grant-type_test.js +++ /dev/null @@ -1,66 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var PasswordGrantType = require('../../../lib/grant-types/password-grant-type'); -var Request = require('../../../lib/request'); -var sinon = require('sinon'); -var should = require('should'); - -/** - * Test `PasswordGrantType`. - */ - -describe('PasswordGrantType', function() { - describe('getUser()', function() { - it('should call `model.getUser()`', function() { - var model = { - getUser: sinon.stub().returns(true), - saveToken: function() {} - }; - var handler = new PasswordGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: { username: 'foo', password: 'bar' }, headers: {}, method: {}, query: {} }); - - return handler.getUser(request) - .then(function() { - model.getUser.callCount.should.equal(1); - model.getUser.firstCall.args.should.have.length(2); - model.getUser.firstCall.args[0].should.equal('foo'); - model.getUser.firstCall.args[1].should.equal('bar'); - model.getUser.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); - - describe('saveToken()', function() { - it('should call `model.saveToken()`', function() { - var client = {}; - var user = {}; - var model = { - getUser: function() {}, - saveToken: sinon.stub().returns(true) - }; - var handler = new PasswordGrantType({ accessTokenLifetime: 120, model: model }); - - sinon.stub(handler, 'validateScope').returns('foobar'); - sinon.stub(handler, 'generateAccessToken').returns('foo'); - sinon.stub(handler, 'generateRefreshToken').returns('bar'); - sinon.stub(handler, 'getAccessTokenExpiresAt').returns('biz'); - sinon.stub(handler, 'getRefreshTokenExpiresAt').returns('baz'); - - return handler.saveToken(user, client, 'foobar') - .then(function() { - model.saveToken.callCount.should.equal(1); - model.saveToken.firstCall.args.should.have.length(3); - model.saveToken.firstCall.args[0].should.eql({ accessToken: 'foo', accessTokenExpiresAt: 'biz', refreshToken: 'bar', refreshTokenExpiresAt: 'baz', scope: 'foobar' }); - model.saveToken.firstCall.args[1].should.equal(client); - model.saveToken.firstCall.args[2].should.equal(user); - model.saveToken.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); -}); diff --git a/test/unit/grant-types/refresh-token-grant-type.spec.ts b/test/unit/grant-types/refresh-token-grant-type.spec.ts new file mode 100755 index 000000000..7bd821b02 --- /dev/null +++ b/test/unit/grant-types/refresh-token-grant-type.spec.ts @@ -0,0 +1,298 @@ +import * as should from 'should'; +import * as sinon from 'sinon'; +import { RefreshTokenGrantType } from '../../../lib/grant-types/refresh-token-grant-type'; +import { Request } from '../../../lib/request'; + +/** + * Test `RefreshTokenGrantType`. + */ + +describe('RefreshTokenGrantType', () => { + describe('handle()', () => { + it('should revoke the previous token', () => { + const token = { accessToken: 'foo', client: {}, user: {} }; + const model = { + getRefreshToken() { + return token; + }, + saveToken() { + return { accessToken: 'bar', client: {}, user: {} }; + }, + revokeToken: sinon.stub().returns({ + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date().getTime() / 2), + user: {}, + }), + }; + const handler = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: { refresh_token: 'bar' }, + headers: {}, + method: {}, + query: {}, + }); + const client: any = {}; + + return handler + .handle(request, client) + .then(() => { + model.revokeToken.callCount.should.equal(1); + model.revokeToken.firstCall.args.should.have.length(1); + model.revokeToken.firstCall.args[0].should.equal(token); + model.revokeToken.firstCall.thisValue.should.equal(model); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + + describe('getRefreshToken()', () => { + it('should call `model.getRefreshToken()`', () => { + const model = { + getRefreshToken: sinon + .stub() + .returns({ accessToken: 'foo', client: {}, user: {} }), + saveToken() {}, + revokeToken() {}, + }; + const handler = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: { refresh_token: 'bar' }, + headers: {}, + method: {}, + query: {}, + }); + const client: any = {}; + + return handler + .getRefreshToken(request, client) + .then(() => { + model.getRefreshToken.callCount.should.equal(1); + model.getRefreshToken.firstCall.args.should.have.length(1); + model.getRefreshToken.firstCall.args[0].should.equal('bar'); + model.getRefreshToken.firstCall.thisValue.should.equal(model); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + + describe('revokeToken()', () => { + it('should call `model.revokeToken()`', () => { + const model = { + getRefreshToken() {}, + revokeToken: sinon.stub().returns({ + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date().getTime() / 2), + user: {}, + }), + saveToken() {}, + }; + const handler = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const token: any = {}; + + return handler + .revokeToken(token) + .then(() => { + model.revokeToken.callCount.should.equal(1); + model.revokeToken.firstCall.args.should.have.length(1); + model.revokeToken.firstCall.args[0].should.equal(token); + model.revokeToken.firstCall.thisValue.should.equal(model); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + + it('should not call `model.revokeToken()`', () => { + const model = { + getRefreshToken() {}, + revokeToken: sinon.stub().returns({ + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date().getTime() / 2), + user: {}, + }), + saveToken() {}, + }; + const handler = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + alwaysIssueNewRefreshToken: false, + }); + const token: any = {}; + + return handler + .revokeToken(token) + .then(() => { + model.revokeToken.callCount.should.equal(0); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + + it('should not call `model.revokeToken()`', () => { + const model = { + getRefreshToken() {}, + revokeToken: sinon.stub().returns({ + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date().getTime() / 2), + user: {}, + }), + saveToken() {}, + }; + const handler = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + alwaysIssueNewRefreshToken: true, + }); + const token: any = {}; + + return handler + .revokeToken(token) + .then(() => { + model.revokeToken.callCount.should.equal(1); + model.revokeToken.firstCall.args.should.have.length(1); + model.revokeToken.firstCall.args[0].should.equal(token); + model.revokeToken.firstCall.thisValue.should.equal(model); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + + describe('saveToken()', () => { + it('should call `model.saveToken()`', () => { + const client: any = {}; + const user = {}; + const model = { + getRefreshToken() {}, + revokeToken() {}, + saveToken: sinon.stub().returns(true), + }; + const handler: any = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + + sinon.stub(handler, 'generateAccessToken').returns('foo' as any); + sinon.stub(handler, 'generateRefreshToken').returns('bar' as any); + sinon.stub(handler, 'getAccessTokenExpiresAt').returns('biz' as any); + sinon.stub(handler, 'getRefreshTokenExpiresAt').returns('baz' as any); + + return handler + .saveToken(user, client, 'foobar') + .then(() => { + model.saveToken.callCount.should.equal(1); + model.saveToken.firstCall.args.should.have.length(3); + model.saveToken.firstCall.args[0].should.eql({ + accessToken: 'foo', + accessTokenExpiresAt: 'biz', + refreshToken: 'bar', + refreshTokenExpiresAt: 'baz', + scope: 'foobar', + }); + model.saveToken.firstCall.args[1].should.equal(client); + model.saveToken.firstCall.args[2].should.equal(user); + model.saveToken.firstCall.thisValue.should.equal(model); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + + it('should call `model.saveToken()` without refresh token', () => { + const client = {}; + const user = {}; + const model = { + getRefreshToken() {}, + revokeToken() {}, + saveToken: sinon.stub().returns(true), + }; + const handler: any = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + alwaysIssueNewRefreshToken: false, + }); + + sinon.stub(handler, 'generateAccessToken').returns('foo' as any); + sinon.stub(handler, 'generateRefreshToken').returns('bar' as any); + sinon.stub(handler, 'getAccessTokenExpiresAt').returns('biz' as any); + sinon.stub(handler, 'getRefreshTokenExpiresAt').returns('baz' as any); + + return handler + .saveToken(user, client, 'foobar') + .then(() => { + model.saveToken.callCount.should.equal(1); + model.saveToken.firstCall.args.should.have.length(3); + model.saveToken.firstCall.args[0].should.eql({ + accessToken: 'foo', + accessTokenExpiresAt: 'biz', + scope: 'foobar', + }); + model.saveToken.firstCall.args[1].should.equal(client); + model.saveToken.firstCall.args[2].should.equal(user); + model.saveToken.firstCall.thisValue.should.equal(model); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + + it('should call `model.saveToken()` with refresh token', () => { + const client = {}; + const user = {}; + const model = { + getRefreshToken() {}, + revokeToken() {}, + saveToken: sinon.stub().returns(true), + }; + const handler: any = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + alwaysIssueNewRefreshToken: true, + }); + + sinon.stub(handler, 'generateAccessToken').returns('foo' as any); + sinon.stub(handler, 'generateRefreshToken').returns('bar' as any); + sinon.stub(handler, 'getAccessTokenExpiresAt').returns('biz' as any); + sinon.stub(handler, 'getRefreshTokenExpiresAt').returns('baz' as any); + + return handler + .saveToken(user, client, 'foobar') + .then(() => { + model.saveToken.callCount.should.equal(1); + model.saveToken.firstCall.args.should.have.length(3); + model.saveToken.firstCall.args[0].should.eql({ + accessToken: 'foo', + accessTokenExpiresAt: 'biz', + refreshToken: 'bar', + refreshTokenExpiresAt: 'baz', + scope: 'foobar', + }); + model.saveToken.firstCall.args[1].should.equal(client); + model.saveToken.firstCall.args[2].should.equal(user); + model.saveToken.firstCall.thisValue.should.equal(model); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); +}); diff --git a/test/unit/grant-types/refresh-token-grant-type_test.js b/test/unit/grant-types/refresh-token-grant-type_test.js deleted file mode 100644 index e5693ba8f..000000000 --- a/test/unit/grant-types/refresh-token-grant-type_test.js +++ /dev/null @@ -1,200 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var RefreshTokenGrantType = require('../../../lib/grant-types/refresh-token-grant-type'); -var Request = require('../../../lib/request'); -var sinon = require('sinon'); -var should = require('should'); - -/** - * Test `RefreshTokenGrantType`. - */ - -describe('RefreshTokenGrantType', function() { - describe('handle()', function() { - it('should revoke the previous token', function() { - var token = { accessToken: 'foo', client: {}, user: {} }; - var model = { - getRefreshToken: function() { return token; }, - saveToken: function() { return { accessToken: 'bar', client: {}, user: {} }; }, - revokeToken: sinon.stub().returns({ accessToken: 'foo', client: {}, refreshTokenExpiresAt: new Date(new Date() / 2), user: {} }) - }; - var handler = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: { refresh_token: 'bar' }, headers: {}, method: {}, query: {} }); - var client = {}; - - return handler.handle(request, client) - .then(function() { - model.revokeToken.callCount.should.equal(1); - model.revokeToken.firstCall.args.should.have.length(1); - model.revokeToken.firstCall.args[0].should.equal(token); - model.revokeToken.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); - - describe('getRefreshToken()', function() { - it('should call `model.getRefreshToken()`', function() { - var model = { - getRefreshToken: sinon.stub().returns({ accessToken: 'foo', client: {}, user: {} }), - saveToken: function() {}, - revokeToken: function() {} - }; - var handler = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: { refresh_token: 'bar' }, headers: {}, method: {}, query: {} }); - var client = {}; - - return handler.getRefreshToken(request, client) - .then(function() { - model.getRefreshToken.callCount.should.equal(1); - model.getRefreshToken.firstCall.args.should.have.length(1); - model.getRefreshToken.firstCall.args[0].should.equal('bar'); - model.getRefreshToken.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); - - describe('revokeToken()', function() { - it('should call `model.revokeToken()`', function() { - var model = { - getRefreshToken: function() {}, - revokeToken: sinon.stub().returns({ accessToken: 'foo', client: {}, refreshTokenExpiresAt: new Date(new Date() / 2), user: {} }), - saveToken: function() {} - }; - var handler = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - var token = {}; - - return handler.revokeToken(token) - .then(function() { - model.revokeToken.callCount.should.equal(1); - model.revokeToken.firstCall.args.should.have.length(1); - model.revokeToken.firstCall.args[0].should.equal(token); - model.revokeToken.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - - it('should not call `model.revokeToken()`', function() { - var model = { - getRefreshToken: function() {}, - revokeToken: sinon.stub().returns({ accessToken: 'foo', client: {}, refreshTokenExpiresAt: new Date(new Date() / 2), user: {} }), - saveToken: function() {} - }; - var handler = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model, alwaysIssueNewRefreshToken: false }); - var token = {}; - - return handler.revokeToken(token) - .then(function() { - model.revokeToken.callCount.should.equal(0); - }) - .catch(should.fail); - }); - - it('should not call `model.revokeToken()`', function() { - var model = { - getRefreshToken: function() {}, - revokeToken: sinon.stub().returns({ accessToken: 'foo', client: {}, refreshTokenExpiresAt: new Date(new Date() / 2), user: {} }), - saveToken: function() {} - }; - var handler = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model, alwaysIssueNewRefreshToken: true }); - var token = {}; - - return handler.revokeToken(token) - .then(function() { - model.revokeToken.callCount.should.equal(1); - model.revokeToken.firstCall.args.should.have.length(1); - model.revokeToken.firstCall.args[0].should.equal(token); - model.revokeToken.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); - - describe('saveToken()', function() { - it('should call `model.saveToken()`', function() { - var client = {}; - var user = {}; - var model = { - getRefreshToken: function() {}, - revokeToken: function() {}, - saveToken: sinon.stub().returns(true) - }; - var handler = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - - sinon.stub(handler, 'generateAccessToken').returns('foo'); - sinon.stub(handler, 'generateRefreshToken').returns('bar'); - sinon.stub(handler, 'getAccessTokenExpiresAt').returns('biz'); - sinon.stub(handler, 'getRefreshTokenExpiresAt').returns('baz'); - - return handler.saveToken(user, client, 'foobar') - .then(function() { - model.saveToken.callCount.should.equal(1); - model.saveToken.firstCall.args.should.have.length(3); - model.saveToken.firstCall.args[0].should.eql({ accessToken: 'foo', accessTokenExpiresAt: 'biz', refreshToken: 'bar', refreshTokenExpiresAt: 'baz', scope: 'foobar' }); - model.saveToken.firstCall.args[1].should.equal(client); - model.saveToken.firstCall.args[2].should.equal(user); - model.saveToken.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - - it('should call `model.saveToken()` without refresh token', function() { - var client = {}; - var user = {}; - var model = { - getRefreshToken: function() {}, - revokeToken: function() {}, - saveToken: sinon.stub().returns(true) - }; - var handler = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model, alwaysIssueNewRefreshToken: false }); - - sinon.stub(handler, 'generateAccessToken').returns('foo'); - sinon.stub(handler, 'generateRefreshToken').returns('bar'); - sinon.stub(handler, 'getAccessTokenExpiresAt').returns('biz'); - sinon.stub(handler, 'getRefreshTokenExpiresAt').returns('baz'); - - return handler.saveToken(user, client, 'foobar') - .then(function() { - model.saveToken.callCount.should.equal(1); - model.saveToken.firstCall.args.should.have.length(3); - model.saveToken.firstCall.args[0].should.eql({ accessToken: 'foo', accessTokenExpiresAt: 'biz', scope: 'foobar' }); - model.saveToken.firstCall.args[1].should.equal(client); - model.saveToken.firstCall.args[2].should.equal(user); - model.saveToken.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - - it('should call `model.saveToken()` with refresh token', function() { - var client = {}; - var user = {}; - var model = { - getRefreshToken: function() {}, - revokeToken: function() {}, - saveToken: sinon.stub().returns(true) - }; - var handler = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model, alwaysIssueNewRefreshToken: true}); - - sinon.stub(handler, 'generateAccessToken').returns('foo'); - sinon.stub(handler, 'generateRefreshToken').returns('bar'); - sinon.stub(handler, 'getAccessTokenExpiresAt').returns('biz'); - sinon.stub(handler, 'getRefreshTokenExpiresAt').returns('baz'); - - return handler.saveToken(user, client, 'foobar') - .then(function() { - model.saveToken.callCount.should.equal(1); - model.saveToken.firstCall.args.should.have.length(3); - model.saveToken.firstCall.args[0].should.eql({ accessToken: 'foo', accessTokenExpiresAt: 'biz', refreshToken: 'bar', refreshTokenExpiresAt: 'baz', scope: 'foobar' }); - model.saveToken.firstCall.args[1].should.equal(client); - model.saveToken.firstCall.args[2].should.equal(user); - model.saveToken.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); -}); diff --git a/test/unit/handlers/authenticate-handler.spec.ts b/test/unit/handlers/authenticate-handler.spec.ts new file mode 100755 index 000000000..97cef5560 --- /dev/null +++ b/test/unit/handlers/authenticate-handler.spec.ts @@ -0,0 +1,166 @@ +import * as should from 'should'; +import * as sinon from 'sinon'; +import { ServerError } from '../../../lib/errors/server-error'; +import { AuthenticateHandler } from '../../../lib/handlers/authenticate-handler'; +import { Request } from '../../../lib/request'; + +/** + * Test `AuthenticateHandler`. + */ + +describe('AuthenticateHandler', () => { + describe('getTokenFromRequest()', () => { + describe('with bearer token in the request authorization header', () => { + it('should call `getTokenFromRequestHeader()`', () => { + const handler: any = new AuthenticateHandler({ + model: { getAccessToken() {} }, + }); + const request = new Request({ + body: {}, + headers: { Authorization: 'Bearer foo' }, + method: {}, + query: {}, + }); + + sinon.stub(handler, 'getTokenFromRequestHeader'); + + handler.getTokenFromRequest(request); + + handler.getTokenFromRequestHeader.callCount.should.equal(1); + handler.getTokenFromRequestHeader.firstCall.args[0].should.equal( + request, + ); + handler.getTokenFromRequestHeader.restore(); + }); + }); + + describe('with bearer token in the request query', () => { + it('should call `getTokenFromRequestQuery()`', () => { + const handler: any = new AuthenticateHandler({ + model: { getAccessToken() {} }, + }); + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: { access_token: 'foo' }, + }); + + sinon.stub(handler, 'getTokenFromRequestQuery'); + + handler.getTokenFromRequest(request); + + handler.getTokenFromRequestQuery.callCount.should.equal(1); + handler.getTokenFromRequestQuery.firstCall.args[0].should.equal( + request, + ); + handler.getTokenFromRequestQuery.restore(); + }); + }); + + describe('with bearer token in the request body', () => { + it('should call `getTokenFromRequestBody()`', () => { + const handler: any = new AuthenticateHandler({ + model: { getAccessToken() {} }, + }); + const request = new Request({ + body: { access_token: 'foo' }, + headers: {}, + method: {}, + query: {}, + }); + + sinon.stub(handler, 'getTokenFromRequestBody'); + + handler.getTokenFromRequest(request); + + handler.getTokenFromRequestBody.callCount.should.equal(1); + handler.getTokenFromRequestBody.firstCall.args[0].should.equal(request); + handler.getTokenFromRequestBody.restore(); + }); + }); + }); + + describe('getAccessToken()', () => { + it('should call `model.getAccessToken()`', () => { + const model = { + getAccessToken: sinon.stub().returns({ user: {} }), + }; + const handler = new AuthenticateHandler({ model }); + + return handler + .getAccessToken('foo') + .then(() => { + model.getAccessToken.callCount.should.equal(1); + model.getAccessToken.firstCall.args.should.have.length(1); + model.getAccessToken.firstCall.args[0].should.equal('foo'); + model.getAccessToken.firstCall.thisValue.should.equal(model); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + + describe('validateAccessToken()', () => { + it('should fail if token has no valid `accessTokenExpiresAt` date', async () => { + const model = { + getAccessToken() {}, + }; + const handler = new AuthenticateHandler({ model }); + + let failed = false; + try { + await handler.validateAccessToken({ + user: {}, + } as any); + } catch (err) { + err.should.be.an.instanceOf(ServerError); + failed = true; + } + failed.should.equal(true); + }); + + it('should succeed if token has valid `accessTokenExpiresAt` date', () => { + const model = { + getAccessToken() {}, + }; + const handler = new AuthenticateHandler({ model }); + try { + handler.validateAccessToken({ + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + } as any); + } catch (err) { + should.fail('should.fail', ''); + } + }); + }); + + describe('verifyScope()', () => { + it('should call `model.getAccessToken()` if scope is defined', () => { + const model = { + getAccessToken() {}, + verifyScope: sinon.stub().returns(true), + }; + const handler = new AuthenticateHandler({ + addAcceptedScopesHeader: true, + addAuthorizedScopesHeader: true, + model, + scope: 'bar', + }); + + return handler + .verifyScope('foo' as any) + .then(() => { + model.verifyScope.callCount.should.equal(1); + model.verifyScope.firstCall.args.should.have.length(2); + model.verifyScope.firstCall.args[0].should.equal('foo', 'bar'); + model.verifyScope.firstCall.thisValue.should.equal(model); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); +}); diff --git a/test/unit/handlers/authenticate-handler_test.js b/test/unit/handlers/authenticate-handler_test.js deleted file mode 100644 index 2adac7884..000000000 --- a/test/unit/handlers/authenticate-handler_test.js +++ /dev/null @@ -1,153 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var AuthenticateHandler = require('../../../lib/handlers/authenticate-handler'); -var Request = require('../../../lib/request'); -var sinon = require('sinon'); -var should = require('should'); -var ServerError = require('../../../lib/errors/server-error'); - -/** - * Test `AuthenticateHandler`. - */ - -describe('AuthenticateHandler', function() { - describe('getTokenFromRequest()', function() { - describe('with bearer token in the request authorization header', function() { - it('should call `getTokenFromRequestHeader()`', function() { - var handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); - var request = new Request({ - body: {}, - headers: { 'Authorization': 'Bearer foo' }, - method: {}, - query: {} - }); - - sinon.stub(handler, 'getTokenFromRequestHeader'); - - handler.getTokenFromRequest(request); - - handler.getTokenFromRequestHeader.callCount.should.equal(1); - handler.getTokenFromRequestHeader.firstCall.args[0].should.equal(request); - handler.getTokenFromRequestHeader.restore(); - }); - }); - - describe('with bearer token in the request query', function() { - it('should call `getTokenFromRequestQuery()`', function() { - var handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); - var request = new Request({ - body: {}, - headers: {}, - method: {}, - query: { access_token: 'foo' } - }); - - sinon.stub(handler, 'getTokenFromRequestQuery'); - - handler.getTokenFromRequest(request); - - handler.getTokenFromRequestQuery.callCount.should.equal(1); - handler.getTokenFromRequestQuery.firstCall.args[0].should.equal(request); - handler.getTokenFromRequestQuery.restore(); - }); - }); - - describe('with bearer token in the request body', function() { - it('should call `getTokenFromRequestBody()`', function() { - var handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); - var request = new Request({ - body: { access_token: 'foo' }, - headers: {}, - method: {}, - query: {} - }); - - sinon.stub(handler, 'getTokenFromRequestBody'); - - handler.getTokenFromRequest(request); - - handler.getTokenFromRequestBody.callCount.should.equal(1); - handler.getTokenFromRequestBody.firstCall.args[0].should.equal(request); - handler.getTokenFromRequestBody.restore(); - }); - }); - }); - - describe('getAccessToken()', function() { - it('should call `model.getAccessToken()`', function() { - var model = { - getAccessToken: sinon.stub().returns({ user: {} }) - }; - var handler = new AuthenticateHandler({ model: model }); - - return handler.getAccessToken('foo') - .then(function() { - model.getAccessToken.callCount.should.equal(1); - model.getAccessToken.firstCall.args.should.have.length(1); - model.getAccessToken.firstCall.args[0].should.equal('foo'); - model.getAccessToken.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); - - describe('validateAccessToken()', function() { - it('should fail if token has no valid `accessTokenExpiresAt` date', function() { - var model = { - getAccessToken: function() {} - }; - var handler = new AuthenticateHandler({ model: model }); - - var failed = false; - try { - handler.validateAccessToken({ - user: {} - }); - } - catch (err) { - err.should.be.an.instanceOf(ServerError); - failed = true; - } - failed.should.equal(true); - }); - - it('should succeed if token has valid `accessTokenExpiresAt` date', function() { - var model = { - getAccessToken: function() {} - }; - var handler = new AuthenticateHandler({ model: model }); - try { - handler.validateAccessToken({ - user: {}, - accessTokenExpiresAt: new Date(new Date().getTime() + 10000) - }); - } - catch (err) { - should.fail(); - } - }); - }); - - describe('verifyScope()', function() { - it('should call `model.getAccessToken()` if scope is defined', function() { - var model = { - getAccessToken: function() {}, - verifyScope: sinon.stub().returns(true) - }; - var handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: true, model: model, scope: 'bar' }); - - return handler.verifyScope('foo') - .then(function() { - model.verifyScope.callCount.should.equal(1); - model.verifyScope.firstCall.args.should.have.length(2); - model.verifyScope.firstCall.args[0].should.equal('foo', 'bar'); - model.verifyScope.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); -}); diff --git a/test/unit/handlers/authorize-handler.spec.ts b/test/unit/handlers/authorize-handler.spec.ts new file mode 100755 index 000000000..29d9eba36 --- /dev/null +++ b/test/unit/handlers/authorize-handler.spec.ts @@ -0,0 +1,139 @@ +import * as should from 'should'; +import * as sinon from 'sinon'; +import { AuthorizeHandler } from '../../../lib/handlers/authorize-handler'; +import { Request } from '../../../lib/request'; +import { Response } from '../../../lib/response'; + +/** + * Test `AuthorizeHandler`. + */ + +describe('AuthorizeHandler', () => { + describe('generateAuthorizationCode()', () => { + it('should call `model.generateAuthorizationCode()`', async () => { + const model = { + generateAuthorizationCode: sinon.stub().returns({}), + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + try { + await handler.generateAuthorizationCode({}, {}, {}); + model.generateAuthorizationCode.callCount.should.equal(1); + model.generateAuthorizationCode.firstCall.thisValue.should.equal(model); + } catch (error) { + should.fail('should.fail', ''); + } + }); + }); + + describe('getClient()', () => { + it('should call `model.getClient()`', async () => { + const model = { + getAccessToken() {}, + getClient: sinon.stub().returns( + Promise.resolve({ + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }), + ), + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: {}, + query: {}, + }); + try { + await handler.getClient(request); + model.getClient.callCount.should.equal(1); + model.getClient.firstCall.args.should.have.length(1); + model.getClient.firstCall.args[0].should.equal(12345); + model.getClient.firstCall.thisValue.should.equal(model); + } catch (error) { + should.fail('should.fail', ''); + } + }); + }); + + describe('getUser()', () => { + it('should call `authenticateHandler.getUser()`', () => { + const authenticateHandler = { + handle: sinon.stub().returns(Promise.resolve({})), + }; + const model = { + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authenticateHandler, + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: {}, + }); + const response = new Response(); + + return handler + .getUser(request, response) + .then(() => { + authenticateHandler.handle.callCount.should.equal(1); + authenticateHandler.handle.firstCall.args.should.have.length(2); + authenticateHandler.handle.firstCall.args[0].should.equal(request); + authenticateHandler.handle.firstCall.args[1].should.equal(response); + }) + .catch(() => should.fail('should.fail', '')); + }); + }); + + describe('saveAuthorizationCode()', () => { + it('should call `model.saveAuthorizationCode()`', () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode: sinon.stub().returns({}), + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + + return handler + .saveAuthorizationCode( + 'foo', + 'bar' as any, + 'qux', + 'biz' as any, + 'baz', + 'boz' as any, + ) + .then(() => { + model.saveAuthorizationCode.callCount.should.equal(1); + model.saveAuthorizationCode.firstCall.args.should.have.length(3); + model.saveAuthorizationCode.firstCall.args[0].should.eql({ + authorizationCode: 'foo', + expiresAt: 'bar', + redirectUri: 'baz', + scope: 'qux', + }); + model.saveAuthorizationCode.firstCall.args[1].should.equal('biz'); + model.saveAuthorizationCode.firstCall.args[2].should.equal('boz'); + model.saveAuthorizationCode.firstCall.thisValue.should.equal(model); + }) + .catch(() => should.fail('should.fail', '')); + }); + }); +}); diff --git a/test/unit/handlers/authorize-handler_test.js b/test/unit/handlers/authorize-handler_test.js deleted file mode 100644 index fe9b6b1d7..000000000 --- a/test/unit/handlers/authorize-handler_test.js +++ /dev/null @@ -1,102 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var AuthorizeHandler = require('../../../lib/handlers/authorize-handler'); -var Request = require('../../../lib/request'); -var Response = require('../../../lib/response'); -var Promise = require('bluebird'); -var sinon = require('sinon'); -var should = require('should'); - -/** - * Test `AuthorizeHandler`. - */ - -describe('AuthorizeHandler', function() { - describe('generateAuthorizationCode()', function() { - it('should call `model.generateAuthorizationCode()`', function() { - var model = { - generateAuthorizationCode: sinon.stub().returns({}), - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - - return handler.generateAuthorizationCode() - .then(function() { - model.generateAuthorizationCode.callCount.should.equal(1); - model.generateAuthorizationCode.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); - - describe('getClient()', function() { - it('should call `model.getClient()`', function() { - var model = { - getAccessToken: function() {}, - getClient: sinon.stub().returns({ grants: ['authorization_code'], redirectUris: ['http://example.com/cb'] }), - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, method: {}, query: {} }); - - return handler.getClient(request) - .then(function() { - model.getClient.callCount.should.equal(1); - model.getClient.firstCall.args.should.have.length(2); - model.getClient.firstCall.args[0].should.equal(12345); - model.getClient.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); - - describe('getUser()', function() { - it('should call `authenticateHandler.getUser()`', function() { - var authenticateHandler = { handle: sinon.stub().returns(Promise.resolve({})) }; - var model = { - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authenticateHandler: authenticateHandler, authorizationCodeLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - var response = new Response(); - - return handler.getUser(request, response) - .then(function() { - authenticateHandler.handle.callCount.should.equal(1); - authenticateHandler.handle.firstCall.args.should.have.length(2); - authenticateHandler.handle.firstCall.args[0].should.equal(request); - authenticateHandler.handle.firstCall.args[1].should.equal(response); - }) - .catch(should.fail); - }); - }); - - describe('saveAuthorizationCode()', function() { - it('should call `model.saveAuthorizationCode()`', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: sinon.stub().returns({}) - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - - return handler.saveAuthorizationCode('foo', 'bar', 'qux', 'biz', 'baz', 'boz') - .then(function() { - model.saveAuthorizationCode.callCount.should.equal(1); - model.saveAuthorizationCode.firstCall.args.should.have.length(3); - model.saveAuthorizationCode.firstCall.args[0].should.eql({ authorizationCode: 'foo', expiresAt: 'bar', redirectUri: 'baz', scope: 'qux' }); - model.saveAuthorizationCode.firstCall.args[1].should.equal('biz'); - model.saveAuthorizationCode.firstCall.args[2].should.equal('boz'); - model.saveAuthorizationCode.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); -}); diff --git a/test/unit/handlers/token-handler.spec.ts b/test/unit/handlers/token-handler.spec.ts new file mode 100755 index 000000000..fab47fef9 --- /dev/null +++ b/test/unit/handlers/token-handler.spec.ts @@ -0,0 +1,45 @@ +import * as should from 'should'; +import * as sinon from 'sinon'; +import { TokenHandler } from '../../../lib/handlers/token-handler'; +import { Request } from '../../../lib/request'; + +/** + * Test `TokenHandler`. + */ + +describe('TokenHandler', () => { + describe('getClient()', () => { + it('should call `model.getClient()`', () => { + const model = { + getClient: sinon + .stub() + .returns(Promise.resolve({ grants: ['password'] })), + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: {}, + query: {}, + }); + + return handler + .getClient(request, {}) + .then(() => { + model.getClient.callCount.should.equal(1); + model.getClient.firstCall.args.should.have.length(2); + model.getClient.firstCall.args[0].should.equal(12345); + model.getClient.firstCall.args[1].should.equal('secret'); + model.getClient.firstCall.thisValue.should.equal(model); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); +}); diff --git a/test/unit/handlers/token-handler_test.js b/test/unit/handlers/token-handler_test.js deleted file mode 100644 index 2b37cd05a..000000000 --- a/test/unit/handlers/token-handler_test.js +++ /dev/null @@ -1,37 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var Request = require('../../../lib/request'); -var TokenHandler = require('../../../lib/handlers/token-handler'); -var sinon = require('sinon'); -var should = require('should'); - -/** - * Test `TokenHandler`. - */ - -describe('TokenHandler', function() { - describe('getClient()', function() { - it('should call `model.getClient()`', function() { - var model = { - getClient: sinon.stub().returns({ grants: ['password'] }), - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, method: {}, query: {} }); - - return handler.getClient(request) - .then(function() { - model.getClient.callCount.should.equal(1); - model.getClient.firstCall.args.should.have.length(2); - model.getClient.firstCall.args[0].should.equal(12345); - model.getClient.firstCall.args[1].should.equal('secret'); - model.getClient.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); -}); diff --git a/test/unit/models/token-model.spec.ts b/test/unit/models/token-model.spec.ts new file mode 100755 index 000000000..b2fbd7a34 --- /dev/null +++ b/test/unit/models/token-model.spec.ts @@ -0,0 +1,25 @@ +import { TokenModel } from '../../../lib/models/token-model'; + +/** + * Test `Server`. + */ + +describe('Model', () => { + describe('constructor()', () => { + it('should calculate `accessTokenLifetime` if `accessTokenExpiresAt` is set', () => { + const atExpiresAt = new Date(); + atExpiresAt.setHours(new Date().getHours() + 1); + + const data = { + accessToken: 'foo', + client: 'bar', + user: 'tar', + accessTokenExpiresAt: atExpiresAt, + }; + + const model = new TokenModel(data); + model.accessTokenLifetime.should.be.Number(); + model.accessTokenLifetime.should.be.approximately(3600, 2); + }); + }); +}); diff --git a/test/unit/models/token-model_test.js b/test/unit/models/token-model_test.js deleted file mode 100644 index 3d899951c..000000000 --- a/test/unit/models/token-model_test.js +++ /dev/null @@ -1,25 +0,0 @@ -var TokenModel = require('../../../lib/models/token-model'); - -/** - * Test `Server`. - */ - -describe('Model', function() { - describe('constructor()', function() { - it('should calculate `accessTokenLifetime` if `accessTokenExpiresAt` is set', function() { - var atExpiresAt = new Date(); - atExpiresAt.setHours(new Date().getHours() + 1); - - var data = { - accessToken: 'foo', - client: 'bar', - user: 'tar', - accessTokenExpiresAt: atExpiresAt - }; - - var model = new TokenModel(data); - model.accessTokenLifetime.should.be.Number; - model.accessTokenLifetime.should.be.approximately(3600, 2); - }); - }); -}); diff --git a/test/unit/request.spec.ts b/test/unit/request.spec.ts new file mode 100755 index 000000000..16635b1c3 --- /dev/null +++ b/test/unit/request.spec.ts @@ -0,0 +1,170 @@ +import * as should from 'should'; +import { Request } from '../../lib/request'; + +/** + * Test `Request`. + */ + +function generateBaseRequest() { + return { + query: { + foo: 'bar', + } as any, + method: 'GET', + headers: { + bar: 'foo', + } as any, + body: { + foobar: 'barfoo', + } as any, + } as any; +} + +describe('Request', () => { + it('should instantiate with a basic request', () => { + const originalRequest = generateBaseRequest(); + + const request = new Request(originalRequest); + request.headers.should.eql(originalRequest.headers); + request.method.should.eql(originalRequest.method); + request.query.should.eql(originalRequest.query); + request.body.should.eql(originalRequest.body); + }); + + it('should allow a request to be passed without a body', () => { + const originalRequest = generateBaseRequest(); + delete originalRequest.body; + + const request = new Request(originalRequest); + request.headers.should.eql(originalRequest.headers); + request.method.should.eql(originalRequest.method); + request.query.should.eql(originalRequest.query); + request.body.should.eql({}); + }); + + it('should throw if headers are not passed to the constructor', () => { + const originalRequest = generateBaseRequest(); + delete originalRequest.headers; + + (() => { + new Request(originalRequest); + }).should.throw('Missing parameter: `headers`'); + }); + + it('should throw if query string isnt passed to the constructor', () => { + const originalRequest = generateBaseRequest(); + delete originalRequest.query; + + (() => { + new Request(originalRequest); + }).should.throw('Missing parameter: `query`'); + }); + + it('should throw if method isnt passed to the constructor', () => { + const originalRequest = generateBaseRequest(); + delete originalRequest.method; + + (() => { + new Request(originalRequest); + }).should.throw('Missing parameter: `method`'); + }); + + it('should convert all header keys to lowercase', () => { + const originalRequest = generateBaseRequest(); + originalRequest.headers = { + Foo: 'bar', + BAR: 'foo', + } as any; + + const request = new Request(originalRequest); + request.headers.foo.should.eql('bar'); + request.headers.bar.should.eql('foo'); + should.not.exist(request.headers.Foo); + should.not.exist(request.headers.BAR); + }); + + it('should include additional properties passed in the request', () => { + const originalRequest = generateBaseRequest(); + originalRequest.custom = { + newFoo: 'newBar', + }; + + originalRequest.custom2 = { + newBar: 'newFoo', + }; + + const request: any = new Request(originalRequest); + request.headers.should.eql(originalRequest.headers); + request.method.should.eql(originalRequest.method); + request.query.should.eql(originalRequest.query); + request.body.should.eql(originalRequest.body); + request.custom.should.eql(originalRequest.custom); + request.custom2.should.eql(originalRequest.custom2); + }); + + it('should include additional properties passed in the request', () => { + const originalRequest = generateBaseRequest(); + originalRequest.custom = { + newFoo: 'newBar', + }; + + originalRequest.custom2 = { + newBar: 'newFoo', + }; + + const request: any = new Request(originalRequest); + request.headers.should.eql(originalRequest.headers); + request.method.should.eql(originalRequest.method); + request.query.should.eql(originalRequest.query); + request.body.should.eql(originalRequest.body); + request.custom.should.eql(originalRequest.custom); + request.custom2.should.eql(originalRequest.custom2); + }); + + it('should allow getting of headers using `request.get`', () => { + const originalRequest = generateBaseRequest(); + + const request = new Request(originalRequest); + request.get('bar').should.eql(originalRequest.headers.bar); + }); + + it('should allow getting of headers using `request.get`', () => { + const originalRequest = generateBaseRequest(); + + const request = new Request(originalRequest); + request.get('bar').should.eql(originalRequest.headers.bar); + }); + + it('should allow getting of headers using `request.get`', () => { + const originalRequest = generateBaseRequest(); + + const request = new Request(originalRequest); + request.get('bar').should.eql(originalRequest.headers.bar); + }); + + it('should validate the content-type', () => { + const originalRequest = generateBaseRequest(); + originalRequest.headers['content-type'] = + 'application/x-www-form-urlencoded'; + originalRequest.headers['content-length'] = JSON.stringify( + originalRequest.body, + ).length; + + const request = new Request(originalRequest); + request + .is('application/x-www-form-urlencoded') + .should.eql('application/x-www-form-urlencoded'); + }); + + it('should return false if the content-type is invalid', () => { + const originalRequest = generateBaseRequest(); + originalRequest.headers['content-type'] = + 'application/x-www-form-urlencoded'; + originalRequest.headers['content-length'] = JSON.stringify( + originalRequest.body, + ).length; + + const request = new Request(originalRequest); + request.is('application/json').should.be.false(); + }); +}); diff --git a/test/unit/request_test.js b/test/unit/request_test.js deleted file mode 100644 index 458cb8f9e..000000000 --- a/test/unit/request_test.js +++ /dev/null @@ -1,168 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var Request = require('../../lib/request'); -var should = require('should'); - -/** - * Test `Request`. - */ - -function generateBaseRequest() { - return { - query: { - foo: 'bar' - }, - method: 'GET', - headers: { - bar: 'foo' - }, - body: { - foobar: 'barfoo' - } - }; -} - -describe('Request', function() { - it('should instantiate with a basic request', function() { - var originalRequest = generateBaseRequest(); - - var request = new Request(originalRequest); - request.headers.should.eql(originalRequest.headers); - request.method.should.eql(originalRequest.method); - request.query.should.eql(originalRequest.query); - request.body.should.eql(originalRequest.body); - }); - - it('should allow a request to be passed without a body', function() { - var originalRequest = generateBaseRequest(); - delete originalRequest.body; - - var request = new Request(originalRequest); - request.headers.should.eql(originalRequest.headers); - request.method.should.eql(originalRequest.method); - request.query.should.eql(originalRequest.query); - request.body.should.eql({}); - }); - - it('should throw if headers are not passed to the constructor', function() { - var originalRequest = generateBaseRequest(); - delete originalRequest.headers; - - (function() { - new Request(originalRequest); - }).should.throw('Missing parameter: `headers`'); - }); - - it('should throw if query string isn\'t passed to the constructor', function() { - var originalRequest = generateBaseRequest(); - delete originalRequest.query; - - (function() { - new Request(originalRequest); - }).should.throw('Missing parameter: `query`'); - }); - - it('should throw if method isn\'t passed to the constructor', function() { - var originalRequest = generateBaseRequest(); - delete originalRequest.method; - - (function() { - new Request(originalRequest); - }).should.throw('Missing parameter: `method`'); - }); - - it('should convert all header keys to lowercase', function() { - var originalRequest = generateBaseRequest(); - originalRequest.headers = { - Foo: 'bar', - BAR: 'foo' - }; - - var request = new Request(originalRequest); - request.headers.foo.should.eql('bar'); - request.headers.bar.should.eql('foo'); - should.not.exist(request.headers.Foo); - should.not.exist(request.headers.BAR); - }); - - it('should include additional properties passed in the request', function() { - var originalRequest = generateBaseRequest(); - originalRequest.custom = { - newFoo: 'newBar' - }; - - originalRequest.custom2 = { - newBar: 'newFoo' - }; - - var request = new Request(originalRequest); - request.headers.should.eql(originalRequest.headers); - request.method.should.eql(originalRequest.method); - request.query.should.eql(originalRequest.query); - request.body.should.eql(originalRequest.body); - request.custom.should.eql(originalRequest.custom); - request.custom2.should.eql(originalRequest.custom2); - }); - - it('should include additional properties passed in the request', function() { - var originalRequest = generateBaseRequest(); - originalRequest.custom = { - newFoo: 'newBar' - }; - - originalRequest.custom2 = { - newBar: 'newFoo' - }; - - var request = new Request(originalRequest); - request.headers.should.eql(originalRequest.headers); - request.method.should.eql(originalRequest.method); - request.query.should.eql(originalRequest.query); - request.body.should.eql(originalRequest.body); - request.custom.should.eql(originalRequest.custom); - request.custom2.should.eql(originalRequest.custom2); - }); - - it('should allow getting of headers using `request.get`', function() { - var originalRequest = generateBaseRequest(); - - var request = new Request(originalRequest); - request.get('bar').should.eql(originalRequest.headers.bar); - }); - - it('should allow getting of headers using `request.get`', function() { - var originalRequest = generateBaseRequest(); - - var request = new Request(originalRequest); - request.get('bar').should.eql(originalRequest.headers.bar); - }); - - it('should allow getting of headers using `request.get`', function() { - var originalRequest = generateBaseRequest(); - - var request = new Request(originalRequest); - request.get('bar').should.eql(originalRequest.headers.bar); - }); - - it('should validate the content-type', function() { - var originalRequest = generateBaseRequest(); - originalRequest.headers['content-type'] = 'application/x-www-form-urlencoded'; - originalRequest.headers['content-length'] = JSON.stringify(originalRequest.body).length; - - var request = new Request(originalRequest); - request.is('application/x-www-form-urlencoded').should.eql('application/x-www-form-urlencoded'); - }); - - it('should return false if the content-type is invalid', function() { - var originalRequest = generateBaseRequest(); - originalRequest.headers['content-type'] = 'application/x-www-form-urlencoded'; - originalRequest.headers['content-length'] = JSON.stringify(originalRequest.body).length; - - var request = new Request(originalRequest); - request.is('application/json').should.eql(false); - }); -}); diff --git a/test/unit/response.spec.ts b/test/unit/response.spec.ts new file mode 100755 index 000000000..2a2c3f9a9 --- /dev/null +++ b/test/unit/response.spec.ts @@ -0,0 +1,114 @@ +import * as should from 'should'; +import { Response } from '../../lib/response'; + +/** + * Test `Request`. + */ + +const generateBaseResponse = () => { + return { + headers: { + bar: 'foo', + } as any, + body: { + foobar: 'barfoo', + } as any, + } as any; +}; + +describe('Response', () => { + it('should instantiate with a basic request', () => { + const originalResponse = generateBaseResponse(); + + const response = new Response(originalResponse); + response.headers.should.eql(originalResponse.headers); + response.body.should.eql(originalResponse.body); + response.status.should.eql(200); + }); + + it('should allow a response to be passed without a body', () => { + const originalResponse = generateBaseResponse(); + delete originalResponse.body; + + const response = new Response(originalResponse); + response.headers.should.eql(originalResponse.headers); + response.body.should.eql({}); + response.status.should.eql(200); + }); + + it('should allow a response to be passed without headers', () => { + const originalResponse = generateBaseResponse(); + delete originalResponse.headers; + + const response = new Response(originalResponse); + response.headers.should.eql({}); + response.body.should.eql(originalResponse.body); + response.status.should.eql(200); + }); + + it('should convert all header keys to lowercase', () => { + const originalResponse = generateBaseResponse(); + originalResponse.headers = { + Foo: 'bar', + BAR: 'foo', + }; + + const response: any = new Response(originalResponse); + response.headers.foo.should.eql('bar'); + response.headers.bar.should.eql('foo'); + should.not.exist(response.headers.Foo); + should.not.exist(response.headers.BAR); + }); + + it('should include additional properties passed in the response', () => { + const originalResponse = generateBaseResponse(); + originalResponse.custom = { + newFoo: 'newBar', + }; + + originalResponse.custom2 = { + newBar: 'newFoo', + }; + + const response: any = new Response(originalResponse); + response.headers.should.eql(originalResponse.headers); + response.body.should.eql(originalResponse.body); + response.custom.should.eql(originalResponse.custom); + response.custom2.should.eql(originalResponse.custom2); + }); + + it('should allow getting of headers using `response.get`', () => { + const originalResponse = generateBaseResponse(); + + const response = new Response(originalResponse); + response.get('bar').should.eql(originalResponse.headers.bar); + }); + + it('should allow getting of headers using `response.get`', () => { + const originalResponse = generateBaseResponse(); + + const response = new Response(originalResponse); + response.get('bar').should.eql(originalResponse.headers.bar); + }); + + it('should allow setting of headers using `response.set`', () => { + const originalResponse = generateBaseResponse(); + + const response: any = new Response(originalResponse); + response.headers.should.eql(originalResponse.headers); + response.set('new_header', 'new_value'); + response.headers.bar.should.eql('foo'); + response.headers.new_header.should.eql('new_value'); + }); + + it('should process redirect', () => { + const originalResponse = generateBaseResponse(); + + const response: any = new Response(originalResponse); + response.headers.should.eql(originalResponse.headers); + response.status.should.eql(200); + response.redirect('http://foo.bar'); + response.headers.location.should.eql('http://foo.bar'); + response.status.should.eql(302); + }); +}); diff --git a/test/unit/response_test.js b/test/unit/response_test.js deleted file mode 100644 index c435e32f7..000000000 --- a/test/unit/response_test.js +++ /dev/null @@ -1,120 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var Response = require('../../lib/response'); -var should = require('should'); - -/** - * Test `Request`. - */ - -function generateBaseResponse() { - return { - headers: { - bar: 'foo' - }, - body: { - foobar: 'barfoo' - } - }; -} - -describe('Request', function() { - it('should instantiate with a basic request', function() { - var originalResponse = generateBaseResponse(); - - var response = new Response(originalResponse); - response.headers.should.eql(originalResponse.headers); - response.body.should.eql(originalResponse.body); - response.status.should.eql(200); - }); - - it('should allow a response to be passed without a body', function() { - var originalResponse = generateBaseResponse(); - delete originalResponse.body; - - var response = new Response(originalResponse); - response.headers.should.eql(originalResponse.headers); - response.body.should.eql({}); - response.status.should.eql(200); - }); - - it('should allow a response to be passed without headers', function() { - var originalResponse = generateBaseResponse(); - delete originalResponse.headers; - - var response = new Response(originalResponse); - response.headers.should.eql({}); - response.body.should.eql(originalResponse.body); - response.status.should.eql(200); - }); - - it('should convert all header keys to lowercase', function() { - var originalResponse = generateBaseResponse(); - originalResponse.headers = { - Foo: 'bar', - BAR: 'foo' - }; - - var response = new Response(originalResponse); - response.headers.foo.should.eql('bar'); - response.headers.bar.should.eql('foo'); - should.not.exist(response.headers.Foo); - should.not.exist(response.headers.BAR); - }); - - it('should include additional properties passed in the response', function() { - var originalResponse = generateBaseResponse(); - originalResponse.custom = { - newFoo: 'newBar' - }; - - originalResponse.custom2 = { - newBar: 'newFoo' - }; - - var response = new Response(originalResponse); - response.headers.should.eql(originalResponse.headers); - response.body.should.eql(originalResponse.body); - response.custom.should.eql(originalResponse.custom); - response.custom2.should.eql(originalResponse.custom2); - }); - - it('should allow getting of headers using `response.get`', function() { - var originalResponse = generateBaseResponse(); - - var response = new Response(originalResponse); - response.get('bar').should.eql(originalResponse.headers.bar); - }); - - it('should allow getting of headers using `response.get`', function() { - var originalResponse = generateBaseResponse(); - - var response = new Response(originalResponse); - response.get('bar').should.eql(originalResponse.headers.bar); - }); - - it('should allow setting of headers using `response.set`', function() { - var originalResponse = generateBaseResponse(); - - var response = new Response(originalResponse); - response.headers.should.eql(originalResponse.headers); - response.set('newheader', 'newvalue'); - response.headers.bar.should.eql('foo'); - response.headers.newheader.should.eql('newvalue'); - }); - - it('should process redirect', function() { - var originalResponse = generateBaseResponse(); - - var response = new Response(originalResponse); - response.headers.should.eql(originalResponse.headers); - response.status.should.eql(200); - response.redirect('http://foo.bar'); - response.headers.location.should.eql('http://foo.bar'); - response.status.should.eql(302); - }); -}); diff --git a/test/unit/server.spec.ts b/test/unit/server.spec.ts new file mode 100755 index 000000000..71af87ccc --- /dev/null +++ b/test/unit/server.spec.ts @@ -0,0 +1,89 @@ +import * as sinon from 'sinon'; +import { AuthenticateHandler } from '../../lib/handlers/authenticate-handler'; +import { AuthorizeHandler } from '../../lib/handlers/authorize-handler'; +import { TokenHandler } from '../../lib/handlers/token-handler'; +import { OAuth2Server as Server } from '../../lib/server'; + +const Authenticate: any = AuthenticateHandler; +const Authorize: any = AuthorizeHandler; +const Token: any = TokenHandler; +/** + * Test `Server`. + */ + +describe('Server', () => { + describe('authenticate()', () => { + it('should call `handle`', () => { + const model = { + getAccessToken() {}, + }; + const server = new Server({ model }); + + sinon.stub(Authenticate.prototype, 'handle').returns(Promise.resolve()); + + server.authenticate('foo' as any); + + Authenticate.prototype.handle.callCount.should.equal(1); + Authenticate.prototype.handle.firstCall.args[0].should.equal('foo'); + Authenticate.prototype.handle.restore(); + }); + + it('should map string passed as `options` to `options.scope`', () => { + const model = { + getAccessToken() {}, + verifyScope() {}, + }; + const server = new Server({ model }); + + sinon.stub(Authenticate.prototype, 'handle').returns(Promise.resolve()); + + server.authenticate('foo' as any, 'bar' as any, 'test'); + + Authenticate.prototype.handle.callCount.should.equal(1); + Authenticate.prototype.handle.firstCall.args[0].should.equal('foo'); + Authenticate.prototype.handle.firstCall.args[1].should.equal('bar'); + Authenticate.prototype.handle.firstCall.thisValue.should.have.property( + 'scope', + 'test', + ); + Authenticate.prototype.handle.restore(); + }); + }); + + describe('authorize()', () => { + it('should call `handle`', async () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const server = new Server({ model }); + + sinon.stub(Authorize.prototype, 'handle').returns(Promise.resolve()); + + await server.authorize('foo' as any, 'bar' as any); + + Authorize.prototype.handle.callCount.should.equal(1); + Authorize.prototype.handle.firstCall.args[0].should.equal('foo'); + Authorize.prototype.handle.restore(); + }); + }); + + describe('token()', () => { + it('should call `handle`', async () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const server = new Server({ model }); + + sinon.stub(Token.prototype, 'handle').returns(Promise.resolve()); + + await server.token('foo' as any, 'bar' as any); + + Token.prototype.handle.callCount.should.equal(1); + Token.prototype.handle.firstCall.args[0].should.equal('foo'); + Token.prototype.handle.restore(); + }); + }); +}); diff --git a/test/unit/server_test.js b/test/unit/server_test.js deleted file mode 100644 index e7c343f0c..000000000 --- a/test/unit/server_test.js +++ /dev/null @@ -1,90 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var AuthenticateHandler = require('../../lib/handlers/authenticate-handler'); -var AuthorizeHandler = require('../../lib/handlers/authorize-handler'); -var Promise = require('bluebird'); -var Server = require('../../lib/server'); -var TokenHandler = require('../../lib/handlers/token-handler'); -var sinon = require('sinon'); - -/** - * Test `Server`. - */ - -describe('Server', function() { - describe('authenticate()', function() { - it('should call `handle`', function() { - var model = { - getAccessToken: function() {} - }; - var server = new Server({ model: model }); - - sinon.stub(AuthenticateHandler.prototype, 'handle').returns(Promise.resolve()); - - server.authenticate('foo'); - - AuthenticateHandler.prototype.handle.callCount.should.equal(1); - AuthenticateHandler.prototype.handle.firstCall.args[0].should.equal('foo'); - AuthenticateHandler.prototype.handle.restore(); - }); - - it('should map string passed as `options` to `options.scope`', function() { - var model = { - getAccessToken: function() {}, - verifyScope: function() {} - }; - var server = new Server({ model: model }); - - sinon.stub(AuthenticateHandler.prototype, 'handle').returns(Promise.resolve()); - - server.authenticate('foo', 'bar', 'test'); - - AuthenticateHandler.prototype.handle.callCount.should.equal(1); - AuthenticateHandler.prototype.handle.firstCall.args[0].should.equal('foo'); - AuthenticateHandler.prototype.handle.firstCall.args[1].should.equal('bar'); - AuthenticateHandler.prototype.handle.firstCall.thisValue.should.have.property('scope', 'test'); - AuthenticateHandler.prototype.handle.restore(); - }); - }); - - describe('authorize()', function() { - it('should call `handle`', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var server = new Server({ model: model }); - - sinon.stub(AuthorizeHandler.prototype, 'handle').returns(Promise.resolve()); - - server.authorize('foo', 'bar'); - - AuthorizeHandler.prototype.handle.callCount.should.equal(1); - AuthorizeHandler.prototype.handle.firstCall.args[0].should.equal('foo'); - AuthorizeHandler.prototype.handle.restore(); - }); - }); - - describe('token()', function() { - it('should call `handle`', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var server = new Server({ model: model }); - - sinon.stub(TokenHandler.prototype, 'handle').returns(Promise.resolve()); - - server.token('foo', 'bar'); - - TokenHandler.prototype.handle.callCount.should.equal(1); - TokenHandler.prototype.handle.firstCall.args[0].should.equal('foo'); - TokenHandler.prototype.handle.restore(); - }); - }); -}); diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100755 index 000000000..c180ac821 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "test", "**/*spec.ts"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100755 index 000000000..f4afb9e1c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "removeComments": true, + // "emitDecoratorMetadata": true, + // "experimentalDecorators": true, + "lib": ["es2017"], + "target": "es2017", + "sourceMap": true, + "importHelpers": true, + "outDir": "./dist", + "baseUrl": ".", + "typeRoots": ["node_modules/@types"] + }, + "exclude": ["node_modules", "./dist"] +} diff --git a/tslint.json b/tslint.json new file mode 100755 index 000000000..f36322d63 --- /dev/null +++ b/tslint.json @@ -0,0 +1,26 @@ +{ + // "defaultSeverity": "error", + "extends": ["tslint:recommended", "tslint:all", "tslint:latest"], + "jsRules": { + "no-unused-expression": true + }, + "rules": { + "arrow-parens": false, + "completed-docs": false, + "file-name-casing": [true, "kebab-case"], + "no-object-literal-type-assertion": [false], + "interface-name": [false], + "max-line-length": [true, 120], + "member-access": [false], + "member-ordering": [false], + "no-default-export": [false], + "no-magic-numbers": [false], + "no-unsafe-any": false, + "object-literal-sort-keys": false, + "ordered-imports": [false], + "quotemark": [true, "single"], + "semicolon": [true, "always", "ignore-bound-class-methods"], + "strict-boolean-expressions": [false] + }, + "rulesDirectory": [] +} From bd988daa0d7ee305e5ca25b76ece562b20798d2f Mon Sep 17 00:00:00 2001 From: Ankit Date: Sun, 21 Apr 2019 15:45:11 +0530 Subject: [PATCH 03/30] added-proposal-for-v5 --- .gitignore | 4 +- .travis.yml | 9 +- docs/Makefile | 0 docs/_static/custom.css | 0 docs/_static/favicon.ico | Bin docs/api/errors/access-denied-error.rst | 0 docs/api/errors/index.rst | 0 docs/api/errors/insufficient-scope-error.rst | 0 docs/api/errors/invalid-argument-error.rst | 0 docs/api/errors/invalid-client-error.rst | 0 docs/api/errors/invalid-grant-error.rst | 0 docs/api/errors/invalid-request-error.rst | 0 docs/api/errors/invalid-scope-error.rst | 0 docs/api/errors/invalid-token-error.rst | 0 docs/api/errors/oauth-error.rst | 0 docs/api/errors/server-error.rst | 0 docs/api/errors/unauthorized-client-error.rst | 0 .../api/errors/unauthorized-request-error.rst | 0 .../errors/unsupported-grant-type-error.rst | 0 .../unsupported-response-type-error.rst | 0 docs/api/oauth2-server.rst | 40 +- docs/api/request.rst | 0 docs/api/response.rst | 0 docs/conf.py | 0 docs/docs/adapters.rst | 0 docs/docs/getting-started.rst | 0 docs/index.rst | 0 docs/make.bat | 0 docs/misc/extension-grants.rst | 0 docs/misc/migrating-v2-to-v3.rst | 24 +- docs/model/overview.rst | 17 + docs/model/spec.rst | 5 +- docs/npm_conf.py | 0 lib/errors/index.ts | 16 + lib/errors/server-error.ts | 2 +- lib/grant-types/abstract-grant-type.ts | 27 +- .../authorization-code-grant-type.ts | 20 +- .../client-credentials-grant-type.ts | 8 +- lib/grant-types/implicit-grant-type.ts | 72 +++ lib/grant-types/index.ts | 5 + lib/grant-types/password-grant-type.ts | 14 +- lib/grant-types/refresh-token-grant-type.ts | 16 +- lib/handlers/authenticate-handler.ts | 19 +- lib/handlers/authorize-handler.ts | 258 ++++---- lib/handlers/index.ts | 4 + lib/handlers/revoke-handler.ts | 338 ++++++++++ lib/handlers/token-handler.ts | 48 +- lib/interfaces/index.ts | 6 + lib/interfaces/model.interface.ts | 30 +- lib/request.ts | 2 +- lib/response-types/code-response-type.ts | 159 ++++- lib/response-types/index.ts | 2 + lib/response-types/token-response-type.ts | 99 ++- lib/response.ts | 8 +- lib/server.ts | 69 ++- lib/token-types/bearer-token-type.ts | 5 +- lib/token-types/mac-token-type.ts | 2 +- lib/utils/fn.ts | 10 + package-lock.json | 36 -- package.json | 7 +- scripts/build-prod.js | 12 + .../authorization-code-grant-type.spec.ts | 36 +- .../handlers/authorize-handler.spec.ts | 579 +++++++++--------- .../handlers/token-handler.spec.ts | 4 +- .../authorization-code-grant-type.spec.ts | 2 +- test/unit/handlers/authorize-handler.spec.ts | 111 ++-- tslint.json | 1 + 67 files changed, 1416 insertions(+), 710 deletions(-) mode change 100755 => 100644 docs/Makefile mode change 100755 => 100644 docs/_static/custom.css mode change 100755 => 100644 docs/_static/favicon.ico mode change 100755 => 100644 docs/api/errors/access-denied-error.rst mode change 100755 => 100644 docs/api/errors/index.rst mode change 100755 => 100644 docs/api/errors/insufficient-scope-error.rst mode change 100755 => 100644 docs/api/errors/invalid-argument-error.rst mode change 100755 => 100644 docs/api/errors/invalid-client-error.rst mode change 100755 => 100644 docs/api/errors/invalid-grant-error.rst mode change 100755 => 100644 docs/api/errors/invalid-request-error.rst mode change 100755 => 100644 docs/api/errors/invalid-scope-error.rst mode change 100755 => 100644 docs/api/errors/invalid-token-error.rst mode change 100755 => 100644 docs/api/errors/oauth-error.rst mode change 100755 => 100644 docs/api/errors/server-error.rst mode change 100755 => 100644 docs/api/errors/unauthorized-client-error.rst mode change 100755 => 100644 docs/api/errors/unauthorized-request-error.rst mode change 100755 => 100644 docs/api/errors/unsupported-grant-type-error.rst mode change 100755 => 100644 docs/api/errors/unsupported-response-type-error.rst mode change 100755 => 100644 docs/api/oauth2-server.rst mode change 100755 => 100644 docs/api/request.rst mode change 100755 => 100644 docs/api/response.rst mode change 100755 => 100644 docs/conf.py mode change 100755 => 100644 docs/docs/adapters.rst mode change 100755 => 100644 docs/docs/getting-started.rst mode change 100755 => 100644 docs/index.rst mode change 100755 => 100644 docs/make.bat mode change 100755 => 100644 docs/misc/extension-grants.rst mode change 100755 => 100644 docs/misc/migrating-v2-to-v3.rst mode change 100755 => 100644 docs/model/overview.rst mode change 100755 => 100644 docs/model/spec.rst mode change 100755 => 100644 docs/npm_conf.py create mode 100644 lib/errors/index.ts create mode 100644 lib/grant-types/implicit-grant-type.ts create mode 100644 lib/grant-types/index.ts create mode 100644 lib/handlers/index.ts create mode 100644 lib/handlers/revoke-handler.ts create mode 100644 lib/interfaces/index.ts create mode 100644 lib/response-types/index.ts create mode 100644 lib/utils/fn.ts create mode 100644 scripts/build-prod.js diff --git a/.gitignore b/.gitignore index 8cac61aeb..51563fc34 100755 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,8 @@ npm-debug.log # tests # /test +/coverage +/.nyc_output # dist /dist @@ -19,4 +21,4 @@ npm-debug.log .DS_Store # conf - +ormconfig.json diff --git a/.travis.yml b/.travis.yml index 45f1da500..1ba9eefaa 100755 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,13 @@ language: node_js node_js: - + - 4 + - 4.0 + - 6 + - 6.0 + - 7 + - 7.0 - 8 - 8.0 - - 10 - - 10.0 sudo: false diff --git a/docs/Makefile b/docs/Makefile old mode 100755 new mode 100644 diff --git a/docs/_static/custom.css b/docs/_static/custom.css old mode 100755 new mode 100644 diff --git a/docs/_static/favicon.ico b/docs/_static/favicon.ico old mode 100755 new mode 100644 diff --git a/docs/api/errors/access-denied-error.rst b/docs/api/errors/access-denied-error.rst old mode 100755 new mode 100644 diff --git a/docs/api/errors/index.rst b/docs/api/errors/index.rst old mode 100755 new mode 100644 diff --git a/docs/api/errors/insufficient-scope-error.rst b/docs/api/errors/insufficient-scope-error.rst old mode 100755 new mode 100644 diff --git a/docs/api/errors/invalid-argument-error.rst b/docs/api/errors/invalid-argument-error.rst old mode 100755 new mode 100644 diff --git a/docs/api/errors/invalid-client-error.rst b/docs/api/errors/invalid-client-error.rst old mode 100755 new mode 100644 diff --git a/docs/api/errors/invalid-grant-error.rst b/docs/api/errors/invalid-grant-error.rst old mode 100755 new mode 100644 diff --git a/docs/api/errors/invalid-request-error.rst b/docs/api/errors/invalid-request-error.rst old mode 100755 new mode 100644 diff --git a/docs/api/errors/invalid-scope-error.rst b/docs/api/errors/invalid-scope-error.rst old mode 100755 new mode 100644 diff --git a/docs/api/errors/invalid-token-error.rst b/docs/api/errors/invalid-token-error.rst old mode 100755 new mode 100644 diff --git a/docs/api/errors/oauth-error.rst b/docs/api/errors/oauth-error.rst old mode 100755 new mode 100644 diff --git a/docs/api/errors/server-error.rst b/docs/api/errors/server-error.rst old mode 100755 new mode 100644 diff --git a/docs/api/errors/unauthorized-client-error.rst b/docs/api/errors/unauthorized-client-error.rst old mode 100755 new mode 100644 diff --git a/docs/api/errors/unauthorized-request-error.rst b/docs/api/errors/unauthorized-request-error.rst old mode 100755 new mode 100644 diff --git a/docs/api/errors/unsupported-grant-type-error.rst b/docs/api/errors/unsupported-grant-type-error.rst old mode 100755 new mode 100644 diff --git a/docs/api/errors/unsupported-response-type-error.rst b/docs/api/errors/unsupported-response-type-error.rst old mode 100755 new mode 100644 diff --git a/docs/api/oauth2-server.rst b/docs/api/oauth2-server.rst old mode 100755 new mode 100644 index 48acf538a..dcc5a4c3e --- a/docs/api/oauth2-server.rst +++ b/docs/api/oauth2-server.rst @@ -128,25 +128,27 @@ Authorizes a token request. **Arguments:** -+-----------------------------------------+-----------------+-----------------------------------------------------------------------------+ -| Name | Type | Description | -+=========================================+=================+=============================================================================+ -| request | :doc:`request` | Request object. | -+-----------------------------------------+-----------------+-----------------------------------------------------------------------------+ -| [request.query.allowed=undefined] | String | ``'false'`` to deny the authorization request (see remarks section). | -+-----------------------------------------+-----------------+-----------------------------------------------------------------------------+ -| response | :doc:`response` | Response object. | -+-----------------------------------------+-----------------+-----------------------------------------------------------------------------+ -| [options={}] | Object | Handler options. | -+-----------------------------------------+-----------------+-----------------------------------------------------------------------------+ -| [options.authenticateHandler=undefined] | Object | The authenticate handler (see remarks section). | -+-----------------------------------------+-----------------+-----------------------------------------------------------------------------+ -| [options.allowEmptyState=false] | Boolean | Allow clients to specify an empty ``state``. | -+-----------------------------------------+-----------------+-----------------------------------------------------------------------------+ -| [options.authorizationCodeLifetime=300] | Number | Lifetime of generated authorization codes in seconds (default = 5 minutes). | -+-----------------------------------------+-----------------+-----------------------------------------------------------------------------+ -| [callback=undefined] | Function | Node-style callback to be used instead of the returned ``Promise``. | -+-----------------------------------------+-----------------+-----------------------------------------------------------------------------+ ++-----------------------------------------+-----------------+--------------------------------------------------------------------------------+ +| Name | Type | Description | ++=========================================+=================+================================================================================+ +| request | :doc:`request` | Request object. | ++-----------------------------------------+-----------------+--------------------------------------------------------------------------------+ +| [request.query.allowed=undefined] | String | ``'false'`` to deny the authorization request (see remarks section). | ++-----------------------------------------+-----------------+--------------------------------------------------------------------------------+ +| response | :doc:`response` | Response object. | ++-----------------------------------------+-----------------+--------------------------------------------------------------------------------+ +| [options={}] | Object | Handler options. | ++-----------------------------------------+-----------------+--------------------------------------------------------------------------------+ +| [options.authenticateHandler=undefined] | Object | The authenticate handler (see remarks section). | ++-----------------------------------------+-----------------+--------------------------------------------------------------------------------+ +| [options.allowEmptyState=false] | Boolean | Allow clients to specify an empty ``state``. | ++-----------------------------------------+-----------------+--------------------------------------------------------------------------------+ +| [options.authorizationCodeLifetime=300] | Number | Lifetime of generated authorization codes in seconds (default = 5 minutes). | ++-----------------------------------------+-----------------+--------------------------------------------------------------------------------+ +| [options.accessTokenLifetime=3600] | Number | Lifetime of generated implicit grant access token in seconds (default = 1 hr). | ++-----------------------------------------+-----------------+--------------------------------------------------------------------------------+ +| [callback=undefined] | Function | Node-style callback to be used instead of the returned ``Promise``. | ++-----------------------------------------+-----------------+--------------------------------------------------------------------------------+ **Return value:** diff --git a/docs/api/request.rst b/docs/api/request.rst old mode 100755 new mode 100644 diff --git a/docs/api/response.rst b/docs/api/response.rst old mode 100755 new mode 100644 diff --git a/docs/conf.py b/docs/conf.py old mode 100755 new mode 100644 diff --git a/docs/docs/adapters.rst b/docs/docs/adapters.rst old mode 100755 new mode 100644 diff --git a/docs/docs/getting-started.rst b/docs/docs/getting-started.rst old mode 100755 new mode 100644 diff --git a/docs/index.rst b/docs/index.rst old mode 100755 new mode 100644 diff --git a/docs/make.bat b/docs/make.bat old mode 100755 new mode 100644 diff --git a/docs/misc/extension-grants.rst b/docs/misc/extension-grants.rst old mode 100755 new mode 100644 diff --git a/docs/misc/migrating-v2-to-v3.rst b/docs/misc/migrating-v2-to-v3.rst old mode 100755 new mode 100644 index 5a3ec6431..9d03c8f28 --- a/docs/misc/migrating-v2-to-v3.rst +++ b/docs/misc/migrating-v2-to-v3.rst @@ -28,19 +28,23 @@ The naming of the exposed middlewares has changed to match the OAuth2 _RFC_ more Server options -------------- -The following server options can be set when instantiating the OAuth service: +The following server options can be set when instantiating the OAuth service: * `addAcceptedScopesHeader`: **default true** Add the `X-Accepted-OAuth-Scopes` header with a list of scopes that will be accepted * `addAuthorizedScopesHeader`: **default true** Add the `X-OAuth-Scopes` header with a list of scopes that the user is authorized for * `allowBearerTokensInQueryString`: **default false** Determine if the bearer token can be included in the query string (i.e. `?access_token=`) for validation calls * `allowEmptyState`: **default false** If true, `state` can be empty or not passed. If false, `state` is required. -* `authorizationCodeLifetime`: **default 300** Default number of milliseconds that the authorization code is active for -* `accessTokenLifetime`: **default 3600** Default number of milliseconds that an access token is valid for -* `refreshTokenLifetime`: **default 1209600** Default number of milliseconds that a refresh token is valid for +* `authorizationCodeLifetime`: **default 300** Default number of seconds that the authorization code is active for +* `accessTokenLifetime`: **default 3600** Default number of seconds that an access token is valid for +* `refreshTokenLifetime`: **default 1209600** Default number of seconds that a refresh token is valid for * `allowExtendedTokenAttributes`: **default false** Allows additional attributes (such as `id_token`) to be included in token responses. -* `requireClientAuthentication`: **default true for all grant types** Allow ability to set client/secret authentication to `false` for a specific grant type. +* `requireClientAuthentication`: **default true for all grant types** Allow ability to set client/secret authentication to `false` for a specific grant type. -The following server options have been removed in v3.0.0 +The following server options have changed behavior in v3.0.0: + + * `accessTokenLifetime` can no longer be set to `null` to indicate a non-expiring token. The recommend alternative is to set accessTokenLifetime to a high value. + +The following server options have been removed in v3.0.0: * `grants`: **removed** (now returned by the `getClient` method). * `debug`: **removed** (not the responsibility of this module). @@ -56,7 +60,7 @@ Model specification * `generateAuthorizationCode()` is **optional** and should return a `String`. * `generateRefreshToken(client, user, scope)` is **optional** and should return a `String`. * `getAccessToken(token)` should return an object with: - + * `accessToken` (`String`) * `accessTokenExpiresAt` (`Date`) * `client` (`Object`), containing at least an `id` property that matches the supplied client @@ -71,7 +75,7 @@ Model specification * `user` (`Object`) * `getClient(clientId, clientSecret)` should return an object with, at minimum: - + * `redirectUris` (`Array`) * `grants` (`Array`) @@ -84,11 +88,11 @@ Model specification * `user` (`Object`) * `getUser(username, password)` should return an object: - + * No longer requires that `id` be returned. * `getUserFromClient(client)` should return an object: - + * No longer requires that `id` be returned. * `grantTypeAllowed()` was **removed**. You can instead: diff --git a/docs/model/overview.rst b/docs/model/overview.rst old mode 100755 new mode 100644 index 5e345abd0..f4363a560 --- a/docs/model/overview.rst +++ b/docs/model/overview.rst @@ -58,6 +58,23 @@ Model functions used by the client credentials grant: - :ref:`Model#getUserFromClient` - :ref:`Model#saveToken` - :ref:`Model#validateScope` +-------- + +.. _ImplicitGrant: + +Implicit Grant +------------------------ + +See :rfc:`Section 4.2 of RFC 6749 <6749#section-4.2>`. + +An implicit grant is used to obtain access tokens optimised for public clients known to operate a particular redirection URI. Usually used for browser-based clients implemented in JavaScript. + +Model functions used by the implicit grant: + +- :ref:`Model#generateAccessToken` +- :ref:`Model#getClient` +- :ref:`Model#saveToken` +- :ref:`Model#validateScope` -------- diff --git a/docs/model/spec.rst b/docs/model/spec.rst old mode 100755 new mode 100644 index 341e50ee9..674f389a5 --- a/docs/model/spec.rst +++ b/docs/model/spec.rst @@ -195,7 +195,7 @@ An ``Object`` representing the access token and associated data. +------------------------------+--------+--------------------------------------------------+ | token.accessToken | String | The access token passed to ``getAccessToken()``. | +------------------------------+--------+--------------------------------------------------+ -| [token.accessTokenExpiresAt] | Date | The expiry time of the access token. | +| token.accessTokenExpiresAt | Date | The expiry time of the access token. | +------------------------------+--------+--------------------------------------------------+ | [token.scope] | String | The authorized scope of the access token. | +------------------------------+--------+--------------------------------------------------+ @@ -399,6 +399,7 @@ This model function is **required** for all grant types. - ``authorization_code`` grant - ``client_credentials`` grant +- ``implicit`` grant - ``refresh_token`` grant - ``password`` grant @@ -553,6 +554,7 @@ This model function is **required** for all grant types. - ``authorization_code`` grant - ``client_credentials`` grant +- ``implicit`` grant - ``refresh_token`` grant - ``password`` grant @@ -865,6 +867,7 @@ This model function is **optional**. If not implemented, any scope is accepted. - ``authorization_code`` grant - ``client_credentials`` grant +- ``implicit`` grant - ``password`` grant **Arguments:** diff --git a/docs/npm_conf.py b/docs/npm_conf.py old mode 100755 new mode 100644 diff --git a/lib/errors/index.ts b/lib/errors/index.ts new file mode 100644 index 000000000..74d0a5d5d --- /dev/null +++ b/lib/errors/index.ts @@ -0,0 +1,16 @@ +export { AccessDeniedError } from './access-denied-error'; +export { InsufficientScopeError } from './insufficient-scope-error'; +export { InvalidArgumentError } from './invalid-argument-error'; +export { InvalidClientError } from './invalid-client-error'; +export { InvalidGrantError } from './invalid-grant-error'; +export { InvalidRequestError } from './invalid-request-error'; +export { InvalidScopeError } from './invalid-scope-error'; +export { InvalidTokenError } from './invalid-token-error'; +export { OAuthError } from './oauth-error'; +export { ServerError } from './server-error'; +export { UnauthorizedClientError } from './unauthorized-client-error'; +export { UnauthorizedRequestError } from './unauthorized-request-error'; +export { UnsupportedGrantTypeError } from './unsupported-grant-type-error'; +export { + UnsupportedResponseTypeError, +} from './unsupported-response-type-error'; diff --git a/lib/errors/server-error.ts b/lib/errors/server-error.ts index 3d3a478f4..776b05e94 100755 --- a/lib/errors/server-error.ts +++ b/lib/errors/server-error.ts @@ -10,6 +10,6 @@ import { OAuthError } from './oauth-error'; export class ServerError extends OAuthError { constructor(message?: string | Error, properties?: any) { - super(message, { code: 503, name: 'server_error', ...properties }); + super(message, { code: 500, name: 'server_error', ...properties }); } } diff --git a/lib/grant-types/abstract-grant-type.ts b/lib/grant-types/abstract-grant-type.ts index 6942a390e..9cebd1f84 100755 --- a/lib/grant-types/abstract-grant-type.ts +++ b/lib/grant-types/abstract-grant-type.ts @@ -1,8 +1,5 @@ -import { InvalidArgumentError } from '../errors/invalid-argument-error'; -import { InvalidScopeError } from '../errors/invalid-scope-error'; -import { Client } from '../interfaces/client.interface'; -import { Model } from '../interfaces/model.interface'; -import { User } from '../interfaces/user.interface'; +import { InvalidArgumentError, InvalidScopeError } from '../errors'; +import { Client, Model, User } from '../interfaces'; import { Request } from '../request'; import * as tokenUtil from '../utils/token-util'; import * as is from '../validator/is'; @@ -63,10 +60,7 @@ export class AbstractGrantType { */ getAccessTokenExpiresAt() { - const expires = new Date(); - expires.setSeconds(expires.getSeconds() + this.accessTokenLifetime); - - return expires; + return new Date(Date.now() + this.accessTokenLifetime * 1000); } /** @@ -74,10 +68,7 @@ export class AbstractGrantType { */ getRefreshTokenExpiresAt() { - const expires = new Date(); - expires.setSeconds(expires.getSeconds() + this.refreshTokenLifetime); - - return expires; + return new Date(Date.now() + this.refreshTokenLifetime * 1000); } /** @@ -97,14 +88,18 @@ export class AbstractGrantType { */ async validateScope(user: User, client: Client, scope: string) { if (this.model.validateScope) { - const sc = await this.model.validateScope(user, client, scope); - if (!sc) { + const validatedScope = await this.model.validateScope( + user, + client, + scope, + ); + if (!validatedScope) { throw new InvalidScopeError( 'Invalid scope: Requested scope is invalid', ); } - return sc; + return validatedScope; } return scope; diff --git a/lib/grant-types/authorization-code-grant-type.ts b/lib/grant-types/authorization-code-grant-type.ts index 33a99f7d8..a7ac73449 100755 --- a/lib/grant-types/authorization-code-grant-type.ts +++ b/lib/grant-types/authorization-code-grant-type.ts @@ -1,13 +1,13 @@ -import { InvalidArgumentError } from '../errors/invalid-argument-error'; -import { InvalidGrantError } from '../errors/invalid-grant-error'; -import { InvalidRequestError } from '../errors/invalid-request-error'; -import { ServerError } from '../errors/server-error'; -import { Client } from '../interfaces/client.interface'; -import { Token } from '../interfaces/token.interface'; -import { User } from '../interfaces/user.interface'; +import { AbstractGrantType } from '.'; +import { + InvalidArgumentError, + InvalidGrantError, + InvalidRequestError, + ServerError, +} from '../errors'; +import { Client, Token, User } from '../interfaces'; import { Request } from '../request'; import * as is from '../validator/is'; -import { AbstractGrantType } from './abstract-grant-type'; export class AuthorizationCodeGrantType extends AbstractGrantType { constructor(options: any = {}) { @@ -41,7 +41,7 @@ export class AuthorizationCodeGrantType extends AbstractGrantType { * @see https://tools.ietf.org/html/rfc6749#section-4.1.3 */ - async handle(request: Request, client) { + async handle(request: Request, client: Client) { if (!request) { throw new InvalidArgumentError('Missing parameter: `request`'); } @@ -65,7 +65,7 @@ export class AuthorizationCodeGrantType extends AbstractGrantType { * Get the authorization code. */ - async getAuthorizationCode(request: Request, client) { + async getAuthorizationCode(request: Request, client: Client) { if (!request.body.code) { throw new InvalidRequestError('Missing parameter: `code`'); } diff --git a/lib/grant-types/client-credentials-grant-type.ts b/lib/grant-types/client-credentials-grant-type.ts index e4d4cebd4..3bf2258ea 100755 --- a/lib/grant-types/client-credentials-grant-type.ts +++ b/lib/grant-types/client-credentials-grant-type.ts @@ -1,10 +1,8 @@ -import { InvalidArgumentError } from '../errors/invalid-argument-error'; -import { InvalidGrantError } from '../errors/invalid-grant-error'; -import { Client } from '../interfaces/client.interface'; +import { AbstractGrantType } from '.'; +import { InvalidArgumentError, InvalidGrantError } from '../errors'; +import { Client, User } from '../interfaces'; import { Token } from '../interfaces/token.interface'; -import { User } from '../interfaces/user.interface'; import { Request } from '../request'; -import { AbstractGrantType } from './abstract-grant-type'; export class ClientCredentialsGrantType extends AbstractGrantType { constructor(options: any = {}) { diff --git a/lib/grant-types/implicit-grant-type.ts b/lib/grant-types/implicit-grant-type.ts new file mode 100644 index 000000000..9f18fc8a1 --- /dev/null +++ b/lib/grant-types/implicit-grant-type.ts @@ -0,0 +1,72 @@ +import { AbstractGrantType } from '.'; +import { InvalidArgumentError } from '../errors'; +import { Client, User } from '../interfaces'; +import { Token } from '../interfaces/token.interface'; +import { Request } from '../request'; + +export class ImplicitGrantType extends AbstractGrantType { + scope: string; + user: User; + constructor(options: any = {}) { + super(options); + + if (!options.model) { + throw new InvalidArgumentError('Missing parameter: `model`'); + } + + if (!options.model.saveToken) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `saveToken()`', + ); + } + + if (!options.user) { + throw new InvalidArgumentError('Missing parameter: `user`'); + } + + this.scope = options.scope; + this.user = options.user; + } + + /** + * Handle implicit token grant. + */ + + handle(request: Request, client: Client) { + if (!request) { + throw new InvalidArgumentError('Missing parameter: `request`'); + } + + if (!client) { + throw new InvalidArgumentError('Missing parameter: `client`'); + } + + return this.saveToken(this.user, client, this.scope); + } + + /** + * Save token. + */ + + async saveToken(user: User, client: Client, scope: string) { + const fns = [ + this.validateScope(user, client, scope), + this.generateAccessToken(client, user, scope), + this.getAccessTokenExpiresAt(), + ]; + + const [ + validatedScope, + accessToken, + accessTokenExpiresAt, + ] = await Promise.all(fns as any); + + const token = { + accessToken, + accessTokenExpiresAt, + scope: validatedScope, + } as Token; + + return this.model.saveToken(token, client, user); + } +} diff --git a/lib/grant-types/index.ts b/lib/grant-types/index.ts new file mode 100644 index 000000000..d766ce46a --- /dev/null +++ b/lib/grant-types/index.ts @@ -0,0 +1,5 @@ +export { AbstractGrantType } from './abstract-grant-type'; +export { AuthorizationCodeGrantType } from './authorization-code-grant-type'; +export { ClientCredentialsGrantType } from './client-credentials-grant-type'; +export { PasswordGrantType } from './password-grant-type'; +export { RefreshTokenGrantType } from './refresh-token-grant-type'; diff --git a/lib/grant-types/password-grant-type.ts b/lib/grant-types/password-grant-type.ts index ff7202d0a..f0c9dc9f7 100755 --- a/lib/grant-types/password-grant-type.ts +++ b/lib/grant-types/password-grant-type.ts @@ -1,12 +1,12 @@ -import { InvalidArgumentError } from '../errors/invalid-argument-error'; -import { InvalidGrantError } from '../errors/invalid-grant-error'; -import { InvalidRequestError } from '../errors/invalid-request-error'; -import { Client } from '../interfaces/client.interface'; -import { Token } from '../interfaces/token.interface'; -import { User } from '../interfaces/user.interface'; +import { AbstractGrantType } from '.'; +import { + InvalidArgumentError, + InvalidGrantError, + InvalidRequestError, +} from '../errors'; +import { Client, Token, User } from '../interfaces'; import { Request } from '../request'; import * as is from '../validator/is'; -import { AbstractGrantType } from './abstract-grant-type'; export class PasswordGrantType extends AbstractGrantType { constructor(options: any = {}) { diff --git a/lib/grant-types/refresh-token-grant-type.ts b/lib/grant-types/refresh-token-grant-type.ts index 827d9a298..2137d7eb4 100755 --- a/lib/grant-types/refresh-token-grant-type.ts +++ b/lib/grant-types/refresh-token-grant-type.ts @@ -1,13 +1,13 @@ -import { InvalidArgumentError } from '../errors/invalid-argument-error'; -import { InvalidGrantError } from '../errors/invalid-grant-error'; -import { InvalidRequestError } from '../errors/invalid-request-error'; -import { ServerError } from '../errors/server-error'; -import { Client } from '../interfaces/client.interface'; -import { RefreshToken } from '../interfaces/refresh-token.interface'; -import { User } from '../interfaces/user.interface'; +import { AbstractGrantType } from '.'; +import { + InvalidArgumentError, + InvalidGrantError, + InvalidRequestError, + ServerError, +} from '../errors'; +import { Client, RefreshToken, User } from '../interfaces'; import { Request } from '../request'; import * as is from '../validator/is'; -import { AbstractGrantType } from './abstract-grant-type'; export class RefreshTokenGrantType extends AbstractGrantType { constructor(options: any = {}) { diff --git a/lib/handlers/authenticate-handler.ts b/lib/handlers/authenticate-handler.ts index d59b8ff27..60b56e050 100755 --- a/lib/handlers/authenticate-handler.ts +++ b/lib/handlers/authenticate-handler.ts @@ -1,12 +1,13 @@ -import { InsufficientScopeError } from '../errors/insufficient-scope-error'; -import { InvalidArgumentError } from '../errors/invalid-argument-error'; -import { InvalidRequestError } from '../errors/invalid-request-error'; -import { InvalidTokenError } from '../errors/invalid-token-error'; -import { OAuthError } from '../errors/oauth-error'; -import { ServerError } from '../errors/server-error'; -import { UnauthorizedRequestError } from '../errors/unauthorized-request-error'; -import { Model } from '../interfaces/model.interface'; -import { Token } from '../interfaces/token.interface'; +import { + InsufficientScopeError, + InvalidArgumentError, + InvalidRequestError, + InvalidTokenError, + OAuthError, + ServerError, + UnauthorizedRequestError, +} from '../errors'; +import { Model, Token } from '../interfaces'; import { Request } from '../request'; import { Response } from '../response'; diff --git a/lib/handlers/authorize-handler.ts b/lib/handlers/authorize-handler.ts index 454482696..e7256332d 100755 --- a/lib/handlers/authorize-handler.ts +++ b/lib/handlers/authorize-handler.ts @@ -1,34 +1,40 @@ -import { has } from 'lodash'; -import { format, parse, UrlWithParsedQuery } from 'url'; -import { AccessDeniedError } from '../errors/access-denied-error'; -import { InvalidArgumentError } from '../errors/invalid-argument-error'; -import { InvalidClientError } from '../errors/invalid-client-error'; -import { InvalidRequestError } from '../errors/invalid-request-error'; -import { InvalidScopeError } from '../errors/invalid-scope-error'; -import { OAuthError } from '../errors/oauth-error'; -import { ServerError } from '../errors/server-error'; -import { UnauthorizedClientError } from '../errors/unauthorized-client-error'; -import { UnsupportedResponseTypeError } from '../errors/unsupported-response-type-error'; -import { AuthenticateHandler } from '../handlers/authenticate-handler'; -import { AuthorizationCode } from '../interfaces/authorization-code.interface'; -import { Client } from '../interfaces/client.interface'; -import { Model } from '../interfaces/model.interface'; -import { User } from '../interfaces/user.interface'; +import * as url from 'url'; +import { AuthenticateHandler } from '.'; +import { + AccessDeniedError, + InvalidArgumentError, + InvalidClientError, + InvalidRequestError, + InvalidScopeError, + OAuthError, + ServerError, + UnauthorizedClientError, + UnsupportedResponseTypeError, +} from '../errors'; +import { Client, Model, User } from '../interfaces'; import { Request } from '../request'; import { Response } from '../response'; -import { CodeResponseType } from '../response-types/code-response-type'; -import * as tokenUtil from '../utils/token-util'; +import { CodeResponseType, TokenResponseType } from '../response-types'; +import { hasOwnProperty } from '../utils/fn'; import * as is from '../validator/is'; +/** + * Response types. + */ + const responseTypes = { code: CodeResponseType, - // token: require('../response-types/token-response-type') + token: TokenResponseType, }; +/** + * Constructor. + */ + export class AuthorizeHandler { + options: any; allowEmptyState: boolean; authenticateHandler: any; - authorizationCodeLifetime: number; model: Model; constructor(options: any = {}) { if (options.authenticateHandler && !options.authenticateHandler.handle) { @@ -37,12 +43,6 @@ export class AuthorizeHandler { ); } - if (!options.authorizationCodeLifetime) { - throw new InvalidArgumentError( - 'Missing parameter: `authorizationCodeLifetime`', - ); - } - if (!options.model) { throw new InvalidArgumentError('Missing parameter: `model`'); } @@ -53,16 +53,10 @@ export class AuthorizeHandler { ); } - if (!options.model.saveAuthorizationCode) { - throw new InvalidArgumentError( - 'Invalid argument: model does not implement `saveAuthorizationCode()`', - ); - } - + this.options = options; this.allowEmptyState = options.allowEmptyState; this.authenticateHandler = options.authenticateHandler || new AuthenticateHandler(options); - this.authorizationCodeLifetime = options.authorizationCodeLifetime; this.model = options.model; } @@ -89,72 +83,49 @@ export class AuthorizeHandler { ); } - const fns = [ - this.getAuthorizationCodeLifetime(), - this.getClient(request), - this.getUser(request, response), - ]; + // Extend model object with request + // this.model.request = request; - const [expiresAt, client, user] = await Promise.all(fns); - const uri = this.getRedirectUri(request, client); - let scope: any; - let state: any; - let ResponseType: any; + const fns = [this.getClient(request), this.getUser(request, response)]; + // try { + const [client, user] = await Promise.all(fns); + let scope: string; + let state: string; + let RequestedResponseType: any; + let responseType: any; + const uri = this.getRedirectUri(request, client); try { - scope = this.getScope(request); - const authorizationCode = await this.generateAuthorizationCode( - client, - user, - scope, - ); + const requestedScope = this.getScope(request); + + const validScope = await this.validateScope(user, client, requestedScope); + scope = validScope; state = this.getState(request); - ResponseType = this.getResponseType(request); - const code = await this.saveAuthorizationCode( - authorizationCode, - expiresAt, - scope, + RequestedResponseType = this.getResponseType(request, client); + responseType = new RequestedResponseType(this.options); + const codeOrAccessToken = await responseType.handle( + request, client, - uri, user, + uri, + scope, ); - const responseType = new ResponseType(code.authorizationCode); const redirectUri = this.buildSuccessRedirectUri(uri, responseType); - this.updateResponse(response, redirectUri, state); + this.updateResponse(response, redirectUri, responseType, state); - return code; + return codeOrAccessToken; } catch (e) { if (!(e instanceof OAuthError)) { e = new ServerError(e); } - const redirectUri = this.buildErrorRedirectUri(uri, e); - this.updateResponse(response, redirectUri, state); - throw e; - } - } - /** - * Generate authorization code. - */ + const redirectUri = this.buildErrorRedirectUri(uri, responseType, e); - generateAuthorizationCode(client, user, scope) { - if (this.model.generateAuthorizationCode) { - return this.model.generateAuthorizationCode(client, user, scope); - } + this.updateResponse(response, redirectUri, responseType, state); - return tokenUtil.GenerateRandomToken(); - } - - /** - * Get authorization code lifetime. - */ - - getAuthorizationCodeLifetime() { - const expires = new Date(); - - expires.setSeconds(expires.getSeconds() + this.authorizationCodeLifetime); - - return expires; + throw e; + } + // } catch (error) {} } /** @@ -191,7 +162,12 @@ export class AuthorizeHandler { throw new InvalidClientError('Invalid client: missing client `grants`'); } - if (!client.grants.includes('authorization_code')) { + const responseType = + request.body.response_type || request.query.response_type; + const requestedGrantType = + responseType === 'token' ? 'implicit' : 'authorization_code'; + + if (!client.grants.includes(requestedGrantType)) { throw new UnauthorizedClientError( 'Unauthorized client: `grant_type` is invalid', ); @@ -212,6 +188,28 @@ export class AuthorizeHandler { return client; } + /** + * Validate requested scope. + */ + async validateScope(user: User, client: Client, scope: string) { + if (this.model.validateScope) { + const validatedScope = await this.model.validateScope( + user, + client, + scope, + ); + if (!validatedScope) { + throw new InvalidScopeError( + 'Invalid scope: Requested scope is invalid', + ); + } + + return validatedScope; + } + + return scope; + } + /** * Get scope from the request. */ @@ -269,7 +267,7 @@ export class AuthorizeHandler { * Get redirect URI. */ - getRedirectUri = (request: Request, client) => { + getRedirectUri = (request: Request, client: Client) => { return ( request.body.redirect_uri || request.query.redirect_uri || @@ -277,33 +275,11 @@ export class AuthorizeHandler { ); }; - /** - * Save authorization code. - */ - - async saveAuthorizationCode( - authorizationCode: string, - expiresAt: Date, - scope: string, - client: Client, - redirectUri: string, - user: User, - ) { - const code = { - authorizationCode, - expiresAt, - redirectUri, - scope, - } as AuthorizationCode; - - return this.model.saveAuthorizationCode(code, client, user); - } - /** * Get response type. */ - getResponseType = (request: Request) => { + getResponseType = (request: Request, client: Client) => { const responseType = request.body.response_type || request.query.response_type; @@ -311,12 +287,21 @@ export class AuthorizeHandler { throw new InvalidRequestError('Missing parameter: `response_type`'); } - if (!has(responseTypes, responseType)) { + if (!hasOwnProperty(responseTypes, responseType)) { throw new UnsupportedResponseTypeError( 'Unsupported response type: `response_type` is not supported', ); } + if ( + responseType === 'token' && + (!client || !client.grants.includes('implicit')) + ) { + throw new UnauthorizedClientError( + 'Unauthorized client: `grant_type` is invalid', + ); + } + return responseTypes[responseType]; }; @@ -326,24 +311,42 @@ export class AuthorizeHandler { buildSuccessRedirectUri = ( redirectUri: string, - responseType: CodeResponseType, + responseType: CodeResponseType | TokenResponseType, ) => { - return responseType.buildRedirectUri(redirectUri); + const uri = url.parse(redirectUri); + + return responseType.buildRedirectUri(uri); }; /** * Build an error response that redirects the user-agent to the client-provided url. */ - buildErrorRedirectUri = (redirectUri: string, error: Error) => { - const uri = parse(redirectUri, true); + buildErrorRedirectUri = ( + redirectUri: any, + responseType: CodeResponseType | TokenResponseType, + error: Error, + ) => { + let uri = url.parse(redirectUri, true); + + if (responseType) { + uri = responseType.setRedirectUriParam(uri, 'error', error.name); - uri.query = { - error: error.name, - }; + if (error.message) { + uri = responseType.setRedirectUriParam( + uri, + 'error_description', + error.message, + ); + } + } else { + uri.query = { + error: error.name, + }; - if (error.message) { - uri.query.error_description = error.message; + if (error.message) { + uri.query.error_description = error.message; + } } return uri; @@ -355,15 +358,22 @@ export class AuthorizeHandler { updateResponse = ( response: Response, - redirectUri: UrlWithParsedQuery, - state: string, + redirectUri: any, + responseType: CodeResponseType | TokenResponseType, + state: any, ) => { - redirectUri.query = redirectUri.query || {}; - - if (state) { + if (responseType && state) { + // tslint:disable-next-line:no-parameter-reassignment + redirectUri = responseType.setRedirectUriParam( + redirectUri, + 'state', + state, + ); + } else if (state) { + redirectUri.query = redirectUri.query || {}; redirectUri.query.state = state; } - response.redirect(format(redirectUri)); + response.redirect(url.format(redirectUri)); }; } diff --git a/lib/handlers/index.ts b/lib/handlers/index.ts new file mode 100644 index 000000000..7bdf800ee --- /dev/null +++ b/lib/handlers/index.ts @@ -0,0 +1,4 @@ +export { AuthenticateHandler } from './authenticate-handler'; +export { AuthorizeHandler } from './authorize-handler'; +export { RevokeHandler } from './revoke-handler'; +export { TokenHandler } from './token-handler'; diff --git a/lib/handlers/revoke-handler.ts b/lib/handlers/revoke-handler.ts new file mode 100644 index 000000000..3026a97bc --- /dev/null +++ b/lib/handlers/revoke-handler.ts @@ -0,0 +1,338 @@ +import * as auth from 'basic-auth'; +import { + InvalidArgumentError, + InvalidClientError, + InvalidRequestError, + InvalidTokenError, + OAuthError, + ServerError, +} from '../errors'; +import { Client, Model } from '../interfaces'; +import { Request } from '../request'; +import { Response } from '../response'; +import { oneSuccess } from '../utils/fn'; +import * as is from '../validator/is'; + +export class RevokeHandler { + model: Model; + constructor(options: any = {}) { + if (!options.model) { + throw new InvalidArgumentError('Missing parameter: `model`'); + } + + if (!options.model.getClient) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `getClient()`', + ); + } + + if (!options.model.getRefreshToken) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `getRefreshToken()`', + ); + } + + if (!options.model.getAccessToken) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `getAccessToken()`', + ); + } + + if (!options.model.revokeToken) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `revokeToken()`', + ); + } + + this.model = options.model; + } + + /** + * Revoke Handler. + */ + + async handle(request: Request, response: Response) { + if (!(request instanceof Request)) { + throw new InvalidArgumentError( + 'Invalid argument: `request` must be an instance of Request', + ); + } + + if (!(response instanceof Response)) { + throw new InvalidArgumentError( + 'Invalid argument: `response` must be an instance of Response', + ); + } + + if (request.method !== 'POST') { + throw new InvalidRequestError('Invalid request: method must be POST'); + } + + if (!request.is('application/x-www-form-urlencoded')) { + throw new InvalidRequestError( + 'Invalid request: content must be application/x-www-form-urlencoded', + ); + } + try { + const client = await this.getClient(request, response); + + return this.handleRevokeToken(request, client); + } catch (e) { + let error = e; + if (!(error instanceof OAuthError)) { + error = new ServerError(error); + } + /** + * All necessary information is conveyed in the response code. + * + * Note: invalid tokens do not cause an error response since the client + * cannot handle such an error in a reasonable way. Moreover, the + * purpose of the revocation request, invalidating the particular token, + * is already achieved. + * @see https://tools.ietf.org/html/rfc7009#section-2.2 + */ + if (!(error instanceof InvalidTokenError)) { + this.updateErrorResponse(response, error); + } + + throw error; + } + } + + /** + * Revoke a refresh or access token. + * + * Handle the revoking of refresh tokens, and access tokens if supported / desirable + * RFC7009 specifies that "If the server is unable to locate the token using + * the given hint, it MUST extend its search across all of its supported token types" + */ + + async handleRevokeToken(request: Request, client: Client) { + const token = await this.getTokenFromRequest(request); + try { + const t = await oneSuccess([ + this.getAccessToken(token, client), + this.getRefreshToken(token, client), + ]); + + return this.revokeToken(t); + } catch (errors) { + throw errors; + } + } + + /** + * Get the client from the model. + */ + + async getClient(request: Request, response: Response) { + const credentials = this.getClientCredentials(request); + + if (!credentials.clientId) { + throw new InvalidRequestError('Missing parameter: `client_id`'); + } + + if (!credentials.clientSecret) { + throw new InvalidRequestError('Missing parameter: `client_secret`'); + } + + if (!is.vschar(credentials.clientId)) { + throw new InvalidRequestError('Invalid parameter: `client_id`'); + } + + if (!is.vschar(credentials.clientSecret)) { + throw new InvalidRequestError('Invalid parameter: `client_secret`'); + } + try { + const client = await this.model.getClient( + credentials.clientId, + credentials.clientSecret, + ); + if (!client) { + throw new InvalidClientError('Invalid client: client is invalid'); + } + + if (!client.grants) { + throw new ServerError('Server error: missing client `grants`'); + } + + if (!(client.grants instanceof Array)) { + throw new ServerError('Server error: `grants` must be an array'); + } + + return client; + } catch (e) { + // Include the "WWW-Authenticate" response header field if the client + // attempted to authenticate via the "Authorization" request header. + // + // @see https://tools.ietf.org/html/rfc6749#section-5.2. + if (e instanceof InvalidClientError && request.get('authorization')) { + response.set('WWW-Authenticate', 'Basic realm="Service"'); + + throw new InvalidClientError(e, { code: 401 }); + } + + throw e; + } + } + + /** + * Get client credentials. + * + * The client credentials may be sent using the HTTP Basic authentication scheme or, alternatively, + * the `client_id` and `client_secret` can be embedded in the body. + * + * @see https://tools.ietf.org/html/rfc6749#section-2.3.1 + */ + + getClientCredentials = (request: Request) => { + const credentials = auth(request as any); + + if (credentials) { + return { clientId: credentials.name, clientSecret: credentials.pass }; + } + + if (request.body.client_id && request.body.client_secret) { + return { + clientId: request.body.client_id, + clientSecret: request.body.client_secret, + }; + } + + throw new InvalidClientError( + 'Invalid client: cannot retrieve client credentials', + ); + }; + + /** + * Get the token from the body. + * + * @see https://tools.ietf.org/html/rfc7009#section-2.1 + */ + + getTokenFromRequest = (request: Request) => { + const bodyToken = request.body.token; + + if (!bodyToken) { + throw new InvalidRequestError('Missing parameter: `token`'); + } + + return bodyToken; + }; + + /** + * Get refresh token. + */ + + async getRefreshToken(token, client: Client) { + const refreshToken = await this.model.getRefreshToken(token); + if (!refreshToken) { + throw new InvalidTokenError('Invalid token: refresh token is invalid'); + } + + if (!refreshToken.client) { + throw new ServerError( + 'Server error: `getRefreshToken()` did not return a `client` object', + ); + } + + if (!refreshToken.user) { + throw new ServerError( + 'Server error: `getRefreshToken()` did not return a `user` object', + ); + } + + if (refreshToken.client.id !== client.id) { + throw new InvalidClientError('Invalid client: client is invalid'); + } + + if ( + refreshToken.refreshTokenExpiresAt && + !(refreshToken.refreshTokenExpiresAt instanceof Date) + ) { + throw new ServerError( + 'Server error: `refreshTokenExpiresAt` must be a Date instance', + ); + } + + if ( + refreshToken.refreshTokenExpiresAt && + refreshToken.refreshTokenExpiresAt < new Date() + ) { + throw new InvalidTokenError('Invalid token: refresh token has expired'); + } + + return refreshToken; + } + + /** + * Get the access token from the model. + */ + + async getAccessToken(token, client) { + const accessToken = await this.model.getAccessToken(token); + if (!accessToken) { + throw new InvalidTokenError('Invalid token: access token is invalid'); + } + + if (!accessToken.client) { + throw new ServerError( + 'Server error: `getAccessToken()` did not return a `client` object', + ); + } + + if (!accessToken.user) { + throw new ServerError( + 'Server error: `getAccessToken()` did not return a `user` object', + ); + } + + if (accessToken.client.id !== client.id) { + throw new InvalidClientError('Invalid client: client is invalid'); + } + + if ( + accessToken.accessTokenExpiresAt && + !(accessToken.accessTokenExpiresAt instanceof Date) + ) { + throw new ServerError('Server error: `expires` must be a Date instance'); + } + + if ( + accessToken.accessTokenExpiresAt && + accessToken.accessTokenExpiresAt < new Date() + ) { + throw new InvalidTokenError('Invalid token: access token has expired.'); + } + + return accessToken; + } + + /** + * Revoke the token. + * + * @see https://tools.ietf.org/html/rfc6749#section-6 + */ + + async revokeToken(token: any) { + const revokedToken = await this.model.revokeToken(token); + if (!revokedToken) { + throw new InvalidTokenError('Invalid token: token is invalid'); + } + + return revokedToken; + } + + /** + * Update response when an error is thrown. + */ + + updateErrorResponse = (response: Response, error: OAuthError) => { + response.body = { + error: error.name, + error_description: error.message, + }; + + response.status = error.code; + }; +} diff --git a/lib/handlers/token-handler.ts b/lib/handlers/token-handler.ts index d51b36c7d..dcf8fe072 100755 --- a/lib/handlers/token-handler.ts +++ b/lib/handlers/token-handler.ts @@ -1,21 +1,27 @@ import * as auth from 'basic-auth'; -import { has } from 'lodash'; -import { InvalidArgumentError } from '../errors/invalid-argument-error'; -import { InvalidClientError } from '../errors/invalid-client-error'; -import { InvalidRequestError } from '../errors/invalid-request-error'; -import { OAuthError } from '../errors/oauth-error'; -import { ServerError } from '../errors/server-error'; -import { UnauthorizedClientError } from '../errors/unauthorized-client-error'; -import { UnsupportedGrantTypeError } from '../errors/unsupported-grant-type-error'; -import { AuthorizationCodeGrantType } from '../grant-types/authorization-code-grant-type'; -import { ClientCredentialsGrantType } from '../grant-types/client-credentials-grant-type'; -import { PasswordGrantType } from '../grant-types/password-grant-type'; -import { RefreshTokenGrantType } from '../grant-types/refresh-token-grant-type'; +import { + InvalidArgumentError, + InvalidClientError, + InvalidRequestError, + OAuthError, + ServerError, + UnauthorizedClientError, + UnsupportedGrantTypeError, +} from '../errors'; +import { + AuthorizationCodeGrantType, + ClientCredentialsGrantType, + PasswordGrantType, + RefreshTokenGrantType, +} from '../grant-types'; +import { Client } from '../interfaces/client.interface'; import { TokenModel } from '../models/token-model'; import { Request } from '../request'; import { Response } from '../response'; import { BearerTokenType } from '../token-types/bearer-token-type'; +import { hasOwnProperty } from '../utils/fn'; import * as is from '../validator/is'; + /** * Grant types. */ @@ -87,9 +93,7 @@ export class TokenHandler { } if (request.method !== 'POST') { - // return Promise.reject( throw new InvalidRequestError('Invalid request: method must be POST'); - // ); } if (!request.is('application/x-www-form-urlencoded')) { @@ -184,8 +188,8 @@ export class TokenHandler { * @see https://tools.ietf.org/html/rfc6749#section-2.3.1 */ - getClientCredentials(request) { - const credentials = auth(request); + getClientCredentials(request: Request) { + const credentials = auth(request as any); const grantType = request.body.grant_type; if (credentials) { @@ -228,7 +232,7 @@ export class TokenHandler { throw new InvalidRequestError('Invalid parameter: `grant_type`'); } - if (!has(this.grantTypes, grantType)) { + if (!hasOwnProperty(this.grantTypes, grantType)) { throw new UnsupportedGrantTypeError( 'Unsupported grant type: `grant_type` is invalid', ); @@ -266,7 +270,7 @@ export class TokenHandler { * Get refresh token lifetime. */ - getRefreshTokenLifetime(client) { + getRefreshTokenLifetime(client: Client) { return client.refreshTokenLifetime || this.refreshTokenLifetime; } @@ -274,7 +278,7 @@ export class TokenHandler { * Get token type. */ - getTokenType = model => { + getTokenType = (model: any) => { return new BearerTokenType( model.accessToken, model.accessTokenLifetime, @@ -288,7 +292,7 @@ export class TokenHandler { * Update response when a token is generated. */ - updateSuccessResponse = (response, tokenType) => { + updateSuccessResponse = (response: Response, tokenType: BearerTokenType) => { response.body = tokenType.valueOf(); response.set('Cache-Control', 'no-store'); @@ -299,7 +303,7 @@ export class TokenHandler { * Update response when an error is thrown. */ - updateErrorResponse = (response, error) => { + updateErrorResponse = (response: Response, error: OAuthError) => { response.body = { error: error.name, error_description: error.message, @@ -311,7 +315,7 @@ export class TokenHandler { /** * Given a grant type, check if client authentication is required */ - isClientAuthenticationRequired = grantType => { + isClientAuthenticationRequired = (grantType: string) => { if (Object.keys(this.requireClientAuthentication).length > 0) { return typeof this.requireClientAuthentication[grantType] !== 'undefined' ? this.requireClientAuthentication[grantType] diff --git a/lib/interfaces/index.ts b/lib/interfaces/index.ts new file mode 100644 index 000000000..a5cb2aeac --- /dev/null +++ b/lib/interfaces/index.ts @@ -0,0 +1,6 @@ +export { AuthorizationCode } from './authorization-code.interface'; +export { Client } from './client.interface'; +export { Model } from './model.interface'; +export { RefreshToken } from './refresh-token.interface'; +export { Token } from './token.interface'; +export { User } from './user.interface'; diff --git a/lib/interfaces/model.interface.ts b/lib/interfaces/model.interface.ts index 428a43e82..bcecc4e10 100644 --- a/lib/interfaces/model.interface.ts +++ b/lib/interfaces/model.interface.ts @@ -12,7 +12,7 @@ export interface BaseModel { generateAccessToken?( client: Client, user: User, - scope: string | string[], + scope: string, ): Promise; /** @@ -41,7 +41,7 @@ export interface RequestAuthenticationModel { * the provided access token was authorized the requested scopes. * */ - verifyScope(token: Token, scope: string | string[]): Promise; + verifyScope(token: Token, scope: string): Promise; } export interface AuthorizationCodeModel @@ -54,7 +54,7 @@ export interface AuthorizationCodeModel generateRefreshToken?( client: Client, user: User, - scope: string | string[], + scope: string, ): Promise; /** @@ -64,7 +64,7 @@ export interface AuthorizationCodeModel generateAuthorizationCode?( client: Client, user: User, - scope: string | string[], + scope: string, ): Promise; /** @@ -95,11 +95,7 @@ export interface AuthorizationCodeModel * valid for a particular client/user combination. * */ - validateScope?( - user: User, - client: Client, - scope: string | string[], - ): Promise; + validateScope?(user: User, client: Client, scope: string): Promise; } export interface PasswordModel extends BaseModel, RequestAuthenticationModel { @@ -110,7 +106,7 @@ export interface PasswordModel extends BaseModel, RequestAuthenticationModel { generateRefreshToken?( client: Client, user: User, - scope: string | string[], + scope: string, ): Promise; /** @@ -125,11 +121,7 @@ export interface PasswordModel extends BaseModel, RequestAuthenticationModel { * is valid for a particular client/user combination. * */ - validateScope?( - user: User, - client: Client, - scope: string | string[], - ): Promise; + validateScope?(user: User, client: Client, scope: string): Promise; } export interface RefreshTokenModel @@ -142,7 +134,7 @@ export interface RefreshTokenModel generateRefreshToken?( client: Client, user: User, - scope: string | string[], + scope: string, ): Promise; /** @@ -171,11 +163,7 @@ export interface ClientCredentialsModel * Invoked to check if the requested scope is valid for a particular client/user combination. * */ - validateScope?( - user: User, - client: Client, - scope: string | string[], - ): Promise; + validateScope?(user: User, client: Client, scope: string): Promise; } export interface ExtensionModel extends BaseModel, RequestAuthenticationModel {} diff --git a/lib/request.ts b/lib/request.ts index 803e415b2..9163ca5ad 100755 --- a/lib/request.ts +++ b/lib/request.ts @@ -1,5 +1,5 @@ import * as typeis from 'type-is'; -import { InvalidArgumentError } from './errors/invalid-argument-error'; +import { InvalidArgumentError } from './errors'; export class Request { body: any; diff --git a/lib/response-types/code-response-type.ts b/lib/response-types/code-response-type.ts index 508b7f4a9..26192be97 100755 --- a/lib/response-types/code-response-type.ts +++ b/lib/response-types/code-response-type.ts @@ -1,29 +1,162 @@ -import { parse } from 'url'; -import { InvalidArgumentError } from '../errors/invalid-argument-error'; - +import { InvalidArgumentError } from '../errors'; +import { AuthorizationCode, Client, Model, User } from '../interfaces'; +import { Request } from '../request'; +import * as tokenUtil from '../utils/token-util'; export class CodeResponseType { code: any; - constructor(code: number) { - if (!code) { - throw new InvalidArgumentError('Missing parameter: `code`'); + authorizationCodeLifetime: number; + model: Model; + constructor(options: any = {}) { + if (!options.authorizationCodeLifetime) { + throw new InvalidArgumentError( + 'Missing parameter: `authorizationCodeLifetime`', + ); + } + + if (!options.model) { + throw new InvalidArgumentError('Missing parameter: `model`'); + } + + if (!options.model.saveAuthorizationCode) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `saveAuthorizationCode()`', + ); } - this.code = code; + + this.code = undefined; + this.authorizationCodeLifetime = options.authorizationCodeLifetime; + this.model = options.model; + } + + /** + * Handle code response type. + */ + + async handle( + request: Request, + client: Client, + user: User, + uri: string, + scope: string, + ) { + if (!request) { + throw new InvalidArgumentError('Missing parameter: `request`'); + } + + if (!client) { + throw new InvalidArgumentError('Missing parameter: `client`'); + } + + if (!user) { + throw new InvalidArgumentError('Missing parameter: `user`'); + } + + if (!uri) { + throw new InvalidArgumentError('Missing parameter: `uri`'); + } + + const authorizationCode = await this.generateAuthorizationCode( + client, + user, + scope, + ); + const expiresAt = this.getAuthorizationCodeExpiresAt(client); + + const code = await this.saveAuthorizationCode( + authorizationCode, + expiresAt, + scope, + client, + uri, + user, + ); + this.code = code.authorizationCode; + + return code; + } + + /** + * Get authorization code expiration date. + */ + + getAuthorizationCodeExpiresAt(client: Client) { + const authorizationCodeLifetime = this.getAuthorizationCodeLifetime(client); + + return new Date(Date.now() + authorizationCodeLifetime * 1000); + } + + /** + * Get authorization code lifetime. + */ + + getAuthorizationCodeLifetime(client: Client) { + return client.authorizationCodeLifetime || this.authorizationCodeLifetime; + } + + /** + * Save authorization code. + */ + + saveAuthorizationCode( + authorizationCode: string, + expiresAt: Date, + scope: string, + client: Client, + redirectUri: any, + user: User, + ) { + const code = { + authorizationCode, + expiresAt, + redirectUri, + scope, + } as AuthorizationCode; + + return this.model.saveAuthorizationCode(code, client, user); + } + + /** + * Generate authorization code. + */ + + async generateAuthorizationCode(client: Client, user: User, scope: string) { + if (this.model.generateAuthorizationCode) { + return this.model.generateAuthorizationCode(client, user, scope); + } + + return tokenUtil.GenerateRandomToken(); } /** * Build redirect uri. */ - buildRedirectUri(redirectUri: string) { + buildRedirectUri(redirectUri: any) { if (!redirectUri) { throw new InvalidArgumentError('Missing parameter: `redirectUri`'); } - const uri = parse(redirectUri, true); + redirectUri.search = undefined; - uri.query.code = this.code; - uri.search = undefined; - - return uri; + return this.setRedirectUriParam(redirectUri, 'code', this.code); } + + /** + * Set redirect uri parameter. + */ + + setRedirectUriParam = (redirectUri: any, key: string, value: string) => { + if (!redirectUri) { + throw new InvalidArgumentError('Missing parameter: `redirectUri`'); + } + + if (!key) { + throw new InvalidArgumentError('Missing parameter: `key`'); + } + + redirectUri.query = redirectUri.query || {}; + redirectUri.query[key] = value; + + return redirectUri; + }; } diff --git a/lib/response-types/index.ts b/lib/response-types/index.ts new file mode 100644 index 000000000..9866921eb --- /dev/null +++ b/lib/response-types/index.ts @@ -0,0 +1,2 @@ +export { CodeResponseType } from './code-response-type'; +export { TokenResponseType } from './token-response-type'; diff --git a/lib/response-types/token-response-type.ts b/lib/response-types/token-response-type.ts index d35927bed..22c2133ae 100755 --- a/lib/response-types/token-response-type.ts +++ b/lib/response-types/token-response-type.ts @@ -1,8 +1,99 @@ -import { ServerError } from '../errors/server-error'; +import { InvalidArgumentError } from '../errors/invalid-argument-error'; +import { ImplicitGrantType } from '../grant-types/implicit-grant-type'; +import { Client } from '../interfaces/client.interface'; +import { Model } from '../interfaces/model.interface'; +import { User } from '../interfaces/user.interface'; +import { Request } from '../request'; -// tslint:disable-next-line:no-unnecessary-class export class TokenResponseType { - constructor() { - throw new ServerError('Not implemented.'); + accessToken: string; + accessTokenLifetime: number; + model: Model; + constructor(options: any = {}) { + if (!options.accessTokenLifetime) { + throw new InvalidArgumentError( + 'Missing parameter: `accessTokenLifetime`', + ); + } + + this.accessToken = undefined; + this.accessTokenLifetime = options.accessTokenLifetime; + this.model = options.model; + } + + /** + * Handle token response type. + */ + + async handle( + request: Request, + client: Client, + user: User, + uri: string, + scope: string, + ) { + if (!request) { + throw new InvalidArgumentError('Missing parameter: `request`'); + } + + if (!client) { + throw new InvalidArgumentError('Missing parameter: `client`'); + } + + const accessTokenLifetime = this.getAccessTokenLifetime(client); + + const options = { + user, + scope, + model: this.model, + accessTokenLifetime, + }; + + const grantType = new ImplicitGrantType(options); + const token = await grantType.handle(request, client); + this.accessToken = token.accessToken; + + return token; } + + /** + * Get access token lifetime. + */ + + getAccessTokenLifetime(client: Client) { + return client.accessTokenLifetime || this.accessTokenLifetime; + } + + /** + * Build redirect uri. + */ + + buildRedirectUri = (redirectUri: any) => { + return this.setRedirectUriParam( + redirectUri, + 'access_token', + this.accessToken, + ); + }; + + /** + * Set redirect uri parameter. + */ + + setRedirectUriParam = (redirectUri: any, key: string, value: any) => { + if (!redirectUri) { + throw new InvalidArgumentError('Missing parameter: `redirectUri`'); + } + + if (!key) { + throw new InvalidArgumentError('Missing parameter: `key`'); + } + + redirectUri.hash = redirectUri.hash || ''; + redirectUri.hash += `${ + redirectUri.hash ? '&' : '' + }${key}=${encodeURIComponent(value)}`; + + return redirectUri; + }; } diff --git a/lib/response.ts b/lib/response.ts index b3cdf8009..b9b576b9b 100755 --- a/lib/response.ts +++ b/lib/response.ts @@ -1,3 +1,5 @@ +import { hasOwnProperty } from './utils/fn'; + export class Response { body: any; headers: any; @@ -9,14 +11,14 @@ export class Response { // Store the headers in lower case. for (const field in options.headers) { - if (options.headers.hasOwnProperty(field)) { + if (hasOwnProperty(options, field)) { this.headers[field.toLowerCase()] = options.headers[field]; } } // Store additional properties of the response object passed in for (const property in options) { - if (options.hasOwnProperty(property) && !this[property]) { + if (hasOwnProperty(options, property) && !this[property]) { this[property] = options[property]; } } @@ -26,7 +28,7 @@ export class Response { * Get a response header. */ - get(field) { + get(field: string) { return this.headers[field.toLowerCase()]; } diff --git a/lib/server.ts b/lib/server.ts index 506fb2693..ba5672f0f 100755 --- a/lib/server.ts +++ b/lib/server.ts @@ -1,21 +1,26 @@ -import { AccessDeniedError } from './errors/access-denied-error'; -import { InsufficientScopeError } from './errors/insufficient-scope-error'; -import { InvalidArgumentError } from './errors/invalid-argument-error'; -import { InvalidClientError } from './errors/invalid-client-error'; -import { InvalidGrantError } from './errors/invalid-grant-error'; -import { InvalidRequestError } from './errors/invalid-request-error'; -import { InvalidScopeError } from './errors/invalid-scope-error'; -import { InvalidTokenError } from './errors/invalid-token-error'; -import { OAuthError } from './errors/oauth-error'; -import { ServerError } from './errors/server-error'; -import { UnauthorizedClientError } from './errors/unauthorized-client-error'; -import { UnauthorizedRequestError } from './errors/unauthorized-request-error'; -import { UnsupportedGrantTypeError } from './errors/unsupported-grant-type-error'; -import { UnsupportedResponseTypeError } from './errors/unsupported-response-type-error'; -import { AbstractGrantType } from './grant-types/abstract-grant-type'; -import { AuthenticateHandler } from './handlers/authenticate-handler'; -import { AuthorizeHandler } from './handlers/authorize-handler'; -import { TokenHandler } from './handlers/token-handler'; +import { + AccessDeniedError, + InsufficientScopeError, + InvalidArgumentError, + InvalidClientError, + InvalidGrantError, + InvalidRequestError, + InvalidScopeError, + InvalidTokenError, + OAuthError, + ServerError, + UnauthorizedClientError, + UnauthorizedRequestError, + UnsupportedGrantTypeError, + UnsupportedResponseTypeError, +} from './errors'; +import { AbstractGrantType } from './grant-types'; +import { + AuthenticateHandler, + AuthorizeHandler, + RevokeHandler, + TokenHandler, +} from './handlers'; import { Request } from './request'; import { Response } from './response'; @@ -32,9 +37,17 @@ export class OAuth2Server { /** * Authenticate a token. */ - authenticate(request: Request, response?: Response, scope?: string); - // tslint:disable-next-line:unified-signatures - authenticate(request: Request, response?: Response, options?: any); + authenticate( + request: Request, + response?: Response, + scope?: string, + ): Promise; + authenticate( + request: Request, + response?: Response, + // tslint:disable-next-line:unified-signatures + options?: any, + ): Promise; async authenticate( request: Request, @@ -62,10 +75,10 @@ export class OAuth2Server { */ async authorize(request: Request, response: Response, options?: any) { - const defaultLifeTime = 300; const opts = { allowEmptyState: false, - authorizationCodeLifetime: defaultLifeTime, + accessTokenLifetime: 60 * 60, + authorizationCodeLifetime: 5 * 60, ...this.options, ...options, }; @@ -90,6 +103,16 @@ export class OAuth2Server { return new TokenHandler(opts).handle(request, response); } + /** + * Revoke a token. + */ + + async revoke(request: Request, response: Response, options: any) { + const opt = { ...this.options, ...options }; + + return new RevokeHandler(opt).handle(request, response); + } + static Request = Request; static Response = Response; static AbstractGrantType = AbstractGrantType; diff --git a/lib/token-types/bearer-token-type.ts b/lib/token-types/bearer-token-type.ts index d58821a77..26967b334 100755 --- a/lib/token-types/bearer-token-type.ts +++ b/lib/token-types/bearer-token-type.ts @@ -1,4 +1,5 @@ -import { InvalidArgumentError } from '../errors/invalid-argument-error'; +import { InvalidArgumentError } from '../errors'; +import { hasOwnProperty } from '../utils/fn'; export class BearerTokenType { accessToken: string; @@ -50,7 +51,7 @@ export class BearerTokenType { } for (const key in this.customAttributes) { - if (this.customAttributes.hasOwnProperty(key)) { + if (hasOwnProperty(this.customAttributes, key)) { object[key] = this.customAttributes[key]; } } diff --git a/lib/token-types/mac-token-type.ts b/lib/token-types/mac-token-type.ts index 730211f95..8210f7c0d 100755 --- a/lib/token-types/mac-token-type.ts +++ b/lib/token-types/mac-token-type.ts @@ -1,4 +1,4 @@ -import { ServerError } from '../errors/server-error'; +import { ServerError } from '../errors'; // tslint:disable-next-line:no-unnecessary-class export class MacTokenType { diff --git a/lib/utils/fn.ts b/lib/utils/fn.ts new file mode 100644 index 000000000..6a8a076cb --- /dev/null +++ b/lib/utils/fn.ts @@ -0,0 +1,10 @@ +const identity = (v: any) => v; + +const reverser = (promise: Promise) => + promise.then(v => Promise.reject(v), identity); + +export const oneSuccess = (promises: Array>) => + Promise.all(promises.map(reverser)).then(e => Promise.reject(e), identity); + +export const hasOwnProperty = (o: any, k: string) => + Object.prototype.hasOwnProperty.call(o, k); diff --git a/package-lock.json b/package-lock.json index e6d18d30f..d17e7b23f 100755 --- a/package-lock.json +++ b/package-lock.json @@ -415,16 +415,6 @@ "is-buffer": "~2.0.3" } }, - "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -466,11 +456,6 @@ "path-is-absolute": "^1.0.0" } }, - "graceful-fs": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" - }, "growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", @@ -602,28 +587,12 @@ "esprima": "^4.0.0" } }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "requires": { - "graceful-fs": "^4.1.6" - } - }, "just-extend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz", "integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==", "dev": true }, - "klaw-sync": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", - "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", - "requires": { - "graceful-fs": "^4.1.11" - } - }, "lcid": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", @@ -1263,11 +1232,6 @@ "integrity": "sha512-FFgHdPt4T/duxx6Ndf7hwgMZZjZpB+U0nMNGVCYPq0rEzWKjEDobm4J6yb3CS7naZ0yURFqdw9Gwc7UOh/P9oQ==", "dev": true }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" - }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", diff --git a/package.json b/package.json index 92760f596..c45637a36 100755 --- a/package.json +++ b/package.json @@ -37,12 +37,10 @@ "main": "index.js", "dependencies": { "basic-auth": "^2.0.1", - "fs-extra": "^7.0.1", - "klaw-sync": "^6.0.0", "lodash": "^4.17.11", "statuses": "^1.5.0", - "tslib": "^1.9.3", - "type-is": "^1.6.16" + "type-is": "^1.6.16", + "tslib": "^1.9.3" }, "devDependencies": { "mocha": "^6.1.3", @@ -68,6 +66,7 @@ "lint:all": "tslint -p tsconfig.json -c tslint.json", "pretest": "npx jshint --config ./.jshintrc lib test", "build": "npx tsc -p tsconfig.build.json", + "build:prod": "npx tsc -p tsconfig.build.json && node ./scripts/build-prod.js", "test": "NODE_ENV=test npx mocha 'test/**/*_test.js'", "test2": "npx mocha 'test/**/*.spec.ts' --config 'test/.mocharc.jsonc'", "test-debug": "NODE_ENV=test npx mocha --inspect --debug-brk 'test/**/*_test.js'" diff --git a/scripts/build-prod.js b/scripts/build-prod.js new file mode 100644 index 000000000..bab2b0a7e --- /dev/null +++ b/scripts/build-prod.js @@ -0,0 +1,12 @@ +const fs = require('fs'); +const packageJson = require('../package.json'); + +delete packageJson.scripts; +delete packageJson.devDependencies; +packageJson.contributors = packageJson.contributors.map( + ({ name, email }) => name + (email ? ` <${email}>` : ''), +); +fs.writeFileSync( + __dirname + '/../dist/package.json', + JSON.stringify(packageJson, null, 2), +); diff --git a/test/integration/grant-types/authorization-code-grant-type.spec.ts b/test/integration/grant-types/authorization-code-grant-type.spec.ts index c089e0039..a7acff399 100755 --- a/test/integration/grant-types/authorization-code-grant-type.spec.ts +++ b/test/integration/grant-types/authorization-code-grant-type.spec.ts @@ -97,7 +97,7 @@ describe('AuthorizationCodeGrantType integration', () => { }); it('should throw an error if `client` is invalid', () => { - const client = {}; + const client: any = {}; const model = { getAuthorizationCode() { return { @@ -165,7 +165,7 @@ describe('AuthorizationCodeGrantType integration', () => { }); it('should return a token', async () => { - const client = { id: 'foobar' }; + const client: any = { id: 'foobar' }; const token = {}; const model = { getAuthorizationCode: () => { @@ -205,7 +205,7 @@ describe('AuthorizationCodeGrantType integration', () => { }); it('should support promises', () => { - const client = { id: 'foobar' }; + const client: any = { id: 'foobar' }; const model = { getAuthorizationCode: () => { return Promise.resolve({ @@ -235,7 +235,7 @@ describe('AuthorizationCodeGrantType integration', () => { }); it('should support non-promises', () => { - const client = { id: 'foobar' }; + const client: any = { id: 'foobar' }; const model = { getAuthorizationCode: () => { return { @@ -265,7 +265,7 @@ describe('AuthorizationCodeGrantType integration', () => { }); // it('should support callbacks', () => { - // const client = { id: 'foobar' }; + // const client: any = { id: 'foobar' }; // const model = { // getAuthorizationCode: (code, callback) => { // callback(undefined, { @@ -304,7 +304,7 @@ describe('AuthorizationCodeGrantType integration', () => { describe('getAuthorizationCode()', () => { it('should throw an error if the request body does not contain `code`', async () => { - const client = {}; + const client: any = {}; const model = { getAuthorizationCode: () => {}, revokeAuthorizationCode: () => {}, @@ -332,7 +332,7 @@ describe('AuthorizationCodeGrantType integration', () => { }); it('should throw an error if `code` is invalid', async () => { - const client = {}; + const client: any = {}; const model = { getAuthorizationCode: () => {}, revokeAuthorizationCode: () => {}, @@ -360,7 +360,7 @@ describe('AuthorizationCodeGrantType integration', () => { }); it('should throw an error if `authorizationCode` is missing', () => { - const client = {}; + const client: any = {}; const model = { getAuthorizationCode: () => {}, revokeAuthorizationCode: () => {}, @@ -389,7 +389,7 @@ describe('AuthorizationCodeGrantType integration', () => { }); it('should throw an error if `authorizationCode.client` is missing', () => { - const client = {}; + const client: any = {}; const model = { getAuthorizationCode: () => { return { authorizationCode: 12345 }; @@ -420,7 +420,7 @@ describe('AuthorizationCodeGrantType integration', () => { }); it('should throw an error if `authorizationCode.expiresAt` is missing', () => { - const client = {}; + const client: any = {}; const model = { getAuthorizationCode: () => { return { authorizationCode: 12345, client: {}, user: {} }; @@ -451,7 +451,7 @@ describe('AuthorizationCodeGrantType integration', () => { }); it('should throw an error if `authorizationCode.user` is missing', () => { - const client = {}; + const client: any = {}; const model = { getAuthorizationCode: () => { return { @@ -486,7 +486,7 @@ describe('AuthorizationCodeGrantType integration', () => { }); it('should throw an error if the client id does not match', () => { - const client = { id: 123 }; + const client: any = { id: 123 }; const model = { getAuthorizationCode() { return { @@ -522,7 +522,7 @@ describe('AuthorizationCodeGrantType integration', () => { }); it('should throw an error if the auth code is expired', () => { - const client = { id: 123 }; + const client: any = { id: 123 }; const date = new Date(new Date().getTime() / 2); const model = { getAuthorizationCode() { @@ -566,7 +566,7 @@ describe('AuthorizationCodeGrantType integration', () => { redirectUri: 'foobar', user: {}, }; - const client = { id: 'foobar' }; + const client: any = { id: 'foobar' }; const model = { getAuthorizationCode() { return authorizationCode; @@ -603,7 +603,7 @@ describe('AuthorizationCodeGrantType integration', () => { expiresAt: new Date(new Date().getTime() * 2), user: {}, }; - const client = { id: 'foobar' }; + const client: any = { id: 'foobar' }; const model = { getAuthorizationCode() { return authorizationCode; @@ -637,7 +637,7 @@ describe('AuthorizationCodeGrantType integration', () => { expiresAt: new Date(new Date().getTime() * 2), user: {}, }; - const client = { id: 'foobar' }; + const client: any = { id: 'foobar' }; const model = { getAuthorizationCode() { return Promise.resolve(authorizationCode); @@ -668,7 +668,7 @@ describe('AuthorizationCodeGrantType integration', () => { expiresAt: new Date(new Date().getTime() * 2), user: {}, }; - const client = { id: 'foobar' }; + const client: any = { id: 'foobar' }; const model = { getAuthorizationCode() { return authorizationCode; @@ -699,7 +699,7 @@ describe('AuthorizationCodeGrantType integration', () => { // expiresAt: new Date(new Date().getTime() * 2), // user: {}, // }; - // const client = { id: 'foobar' }; + // const client: any = { id: 'foobar' }; // const model = { // getAuthorizationCode(code, callback) { // callback(undefined, authorizationCode); diff --git a/test/integration/handlers/authorize-handler.spec.ts b/test/integration/handlers/authorize-handler.spec.ts index b0002600d..d8211baed 100755 --- a/test/integration/handlers/authorize-handler.spec.ts +++ b/test/integration/handlers/authorize-handler.spec.ts @@ -7,7 +7,6 @@ import { InvalidRequestError } from '../../../lib/errors/invalid-request-error'; import { InvalidScopeError } from '../../../lib/errors/invalid-scope-error'; import { ServerError } from '../../../lib/errors/server-error'; import { UnauthorizedClientError } from '../../../lib/errors/unauthorized-client-error'; -import { UnsupportedResponseTypeError } from '../../../lib/errors/unsupported-response-type-error'; import { AuthenticateHandler } from '../../../lib/handlers/authenticate-handler'; import { AuthorizeHandler } from '../../../lib/handlers/authorize-handler'; import { Request } from '../../../lib/request'; @@ -91,19 +90,19 @@ describe('AuthorizeHandler integration', () => { } }); - it('should set the `authorizationCodeLifetime`', () => { - const model = { - getAccessToken: () => {}, - getClient: () => {}, - saveAuthorizationCode: () => {}, - }; - const handler = new AuthorizeHandler({ - authorizationCodeLifetime: 120, - model, - }); + // it('should set the `authorizationCodeLifetime`', () => { + // const model = { + // getAccessToken: () => {}, + // getClient: () => {}, + // saveAuthorizationCode: () => {}, + // }; + // const handler = new AuthorizeHandler({ + // authorizationCodeLifetime: 120, + // model, + // }); - handler.authorizationCodeLifetime.should.equal(120); - }); + // handler.authorizationCodeLifetime.should.equal(120); + // }); it('should set the `authenticateHandler`', () => { const model = { @@ -608,86 +607,86 @@ describe('AuthorizeHandler integration', () => { }); }); - describe('generateAuthorizationCode()', () => { - it('should return an auth code', async () => { - const model = { - getAccessToken() {}, - getClient() {}, - saveAuthorizationCode() {}, - }; - const handler = new AuthorizeHandler({ - authorizationCodeLifetime: 120, - model, - }); - try { - const data: any = await handler.generateAuthorizationCode( - undefined, - undefined, - undefined, - ); - data.should.be.a.sha1(); - } catch (error) { - should.fail('should.fail', ''); - } - }); - - it('should support promises', async () => { - const model = { - generateAuthorizationCode() { - return Promise.resolve({}); - }, - getAccessToken() {}, - getClient() {}, - saveAuthorizationCode() {}, - }; - const handler = new AuthorizeHandler({ - authorizationCodeLifetime: 120, - model, - }); - try { - await handler - .generateAuthorizationCode(undefined, undefined, undefined) - .should.be.an.instanceOf(Promise); - } catch (error) { - should.fail('should.fail', ''); - } - }); - - /* it('should support non-promises', () => { - const model = { - generateAuthorizationCode() { - return {}; - }, - getAccessToken() {}, - getClient() {}, - saveAuthorizationCode() {}, - }; - const handler = new AuthorizeHandler({ - authorizationCodeLifetime: 120, - model, - }); - - handler - .generateAuthorizationCode(undefined, undefined, undefined) - .should.be.an.instanceOf(Promise); - }); */ - }); - - describe('getAuthorizationCodeLifetime()', () => { - it('should return a date', () => { - const model = { - getAccessToken() {}, - getClient() {}, - saveAuthorizationCode() {}, - }; - const handler = new AuthorizeHandler({ - authorizationCodeLifetime: 120, - model, - }); - - handler.getAuthorizationCodeLifetime().should.be.an.instanceOf(Date); - }); - }); + // describe('generateAuthorizationCode()', () => { + // it('should return an auth code', async () => { + // const model = { + // getAccessToken() {}, + // getClient() {}, + // saveAuthorizationCode() {}, + // }; + // const handler = new AuthorizeHandler({ + // authorizationCodeLifetime: 120, + // model, + // }); + // try { + // const data: any = await handler.generateAuthorizationCode( + // undefined, + // undefined, + // undefined, + // ); + // data.should.be.a.sha1(); + // } catch (error) { + // should.fail('should.fail', ''); + // } + // }); + + // it('should support promises', async () => { + // const model = { + // generateAuthorizationCode() { + // return Promise.resolve({}); + // }, + // getAccessToken() {}, + // getClient() {}, + // saveAuthorizationCode() {}, + // }; + // const handler = new AuthorizeHandler({ + // authorizationCodeLifetime: 120, + // model, + // }); + // try { + // await handler + // .generateAuthorizationCode(undefined, undefined, undefined) + // .should.be.an.instanceOf(Promise); + // } catch (error) { + // should.fail('should.fail', ''); + // } + // }); + + // /* it('should support non-promises', () => { + // const model = { + // generateAuthorizationCode() { + // return {}; + // }, + // getAccessToken() {}, + // getClient() {}, + // saveAuthorizationCode() {}, + // }; + // const handler = new AuthorizeHandler({ + // authorizationCodeLifetime: 120, + // model, + // }); + + // handler + // .generateAuthorizationCode(undefined, undefined, undefined) + // .should.be.an.instanceOf(Promise); + // }); */ + // }); + + // describe('getAuthorizationCodeLifetime()', () => { + // it('should return a date', () => { + // const model = { + // getAccessToken() {}, + // getClient() {}, + // saveAuthorizationCode() {}, + // }; + // const handler = new AuthorizeHandler({ + // authorizationCodeLifetime: 120, + // model, + // }); + + // handler.getAuthorizationCodeLifetime().should.be.an.instanceOf(Date); + // }); + // }); describe('getClient()', () => { it('should throw an error if `client_id` is missing', async () => { @@ -1281,201 +1280,201 @@ describe('AuthorizeHandler integration', () => { }); }); - describe('saveAuthorizationCode()', () => { - it('should return an auth code', () => { - const authorizationCode = {}; - const model = { - getAccessToken() {}, - getClient() {}, - saveAuthorizationCode() { - return authorizationCode; - }, - }; - const handler = new AuthorizeHandler({ - authorizationCodeLifetime: 120, - model, - }); - - return handler - .saveAuthorizationCode( - 'foo', - 'bar' as any, - 'biz', - 'baz' as any, - undefined, - undefined, - ) - .then(data => { - data.should.equal(authorizationCode); - }) - .catch(() => should.fail('should.fail', '')); - }); - - it('should support promises when calling `model.saveAuthorizationCode()`', () => { - const model = { - getAccessToken() {}, - getClient() {}, - saveAuthorizationCode() { - return Promise.resolve({}); - }, - }; - const handler = new AuthorizeHandler({ - authorizationCodeLifetime: 120, - model, - }); - - handler - .saveAuthorizationCode( - 'foo', - 'bar' as any, - 'biz', - 'baz' as any, - undefined, - undefined, - ) - .should.be.an.instanceOf(Promise); - }); - - /* it('should support non-promises when calling `model.saveAuthorizationCode()`', () => { - const model = { - getAccessToken() {}, - getClient() {}, - saveAuthorizationCode() { - return {}; - }, - }; - const handler = new AuthorizeHandler({ - authorizationCodeLifetime: 120, - model, - }); - - handler - .saveAuthorizationCode('foo', 'bar', 'biz', 'baz', undefined, undefined) - .should.be.an.instanceOf(Promise); - }); */ - - /* it('should support callbacks when calling `model.saveAuthorizationCode()`', () => { - const model = { - getAccessToken() {}, - getClient() {}, - saveAuthorizationCode(code, client, user, callback) { - return callback(null, true); - }, - }; - const handler = new AuthorizeHandler({ - authorizationCodeLifetime: 120, - model, - }); - - handler - .saveAuthorizationCode('foo', 'bar', 'biz', 'baz') - .should.be.an.instanceOf(Promise); - }); */ - }); - - describe('getResponseType()', () => { - it('should throw an error if `response_type` is missing', () => { - const model = { - getAccessToken() {}, - getClient() {}, - saveAuthorizationCode() {}, - }; - const handler = new AuthorizeHandler({ - authorizationCodeLifetime: 120, - model, - }); - const request = new Request({ - body: {}, - headers: {}, - method: {}, - query: {}, - }); - - try { - handler.getResponseType(request); - - should.fail('should.fail', ''); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Missing parameter: `response_type`'); - } - }); - - it('should throw an error if `response_type` is not `code`', () => { - const model = { - getAccessToken() {}, - getClient() {}, - saveAuthorizationCode() {}, - }; - const handler = new AuthorizeHandler({ - authorizationCodeLifetime: 120, - model, - }); - const request = new Request({ - body: { response_type: 'foobar' }, - headers: {}, - method: {}, - query: {}, - }); - - try { - handler.getResponseType(request); - - should.fail('should.fail', ''); - } catch (e) { - e.should.be.an.instanceOf(UnsupportedResponseTypeError); - e.message.should.equal( - 'Unsupported response type: `response_type` is not supported', - ); - } - }); - - describe('with `response_type` in the request body', () => { - it('should return a response type', () => { - const model = { - getAccessToken() {}, - getClient() {}, - saveAuthorizationCode() {}, - }; - const handler = new AuthorizeHandler({ - authorizationCodeLifetime: 120, - model, - }); - const request = new Request({ - body: { response_type: 'code' }, - headers: {}, - method: {}, - query: {}, - }); - const ResponseType = handler.getResponseType(request); - - ResponseType.should.equal(CodeResponseType); - }); - }); - - describe('with `response_type` in the request query', () => { - it('should return a response type', () => { - const model = { - getAccessToken() {}, - getClient() {}, - saveAuthorizationCode() {}, - }; - const handler = new AuthorizeHandler({ - authorizationCodeLifetime: 120, - model, - }); - const request = new Request({ - body: {}, - headers: {}, - method: {}, - query: { response_type: 'code' }, - }); - const ResponseType = handler.getResponseType(request); - - ResponseType.should.equal(CodeResponseType); - }); - }); - }); + // describe('saveAuthorizationCode()', () => { + // it('should return an auth code', () => { + // const authorizationCode = {}; + // const model = { + // getAccessToken() {}, + // getClient() {}, + // saveAuthorizationCode() { + // return authorizationCode; + // }, + // }; + // const handler = new AuthorizeHandler({ + // authorizationCodeLifetime: 120, + // model, + // }); + + // return handler + // .saveAuthorizationCode( + // 'foo', + // 'bar' as any, + // 'biz', + // 'baz' as any, + // undefined, + // undefined, + // ) + // .then(data => { + // data.should.equal(authorizationCode); + // }) + // .catch(() => should.fail('should.fail', '')); + // }); + + // it('should support promises when calling `model.saveAuthorizationCode()`', () => { + // const model = { + // getAccessToken() {}, + // getClient() {}, + // saveAuthorizationCode() { + // return Promise.resolve({}); + // }, + // }; + // const handler = new AuthorizeHandler({ + // authorizationCodeLifetime: 120, + // model, + // }); + + // handler + // .saveAuthorizationCode( + // 'foo', + // 'bar' as any, + // 'biz', + // 'baz' as any, + // undefined, + // undefined, + // ) + // .should.be.an.instanceOf(Promise); + // }); + + // /* it('should support non-promises when calling `model.saveAuthorizationCode()`', () => { + // const model = { + // getAccessToken() {}, + // getClient() {}, + // saveAuthorizationCode() { + // return {}; + // }, + // }; + // const handler = new AuthorizeHandler({ + // authorizationCodeLifetime: 120, + // model, + // }); + + // handler + // .saveAuthorizationCode('foo', 'bar', 'biz', 'baz', undefined, undefined) + // .should.be.an.instanceOf(Promise); + // }); */ + + // /* it('should support callbacks when calling `model.saveAuthorizationCode()`', () => { + // const model = { + // getAccessToken() {}, + // getClient() {}, + // saveAuthorizationCode(code, client, user, callback) { + // return callback(null, true); + // }, + // }; + // const handler = new AuthorizeHandler({ + // authorizationCodeLifetime: 120, + // model, + // }); + + // handler + // .saveAuthorizationCode('foo', 'bar', 'biz', 'baz') + // .should.be.an.instanceOf(Promise); + // }); */ + // }); + + // describe('getResponseType()', () => { + // it('should throw an error if `response_type` is missing', () => { + // const model = { + // getAccessToken() {}, + // getClient() {}, + // saveAuthorizationCode() {}, + // }; + // const handler = new AuthorizeHandler({ + // authorizationCodeLifetime: 120, + // model, + // }); + // const request = new Request({ + // body: {}, + // headers: {}, + // method: {}, + // query: {}, + // }); + + // try { + // handler.getResponseType(request); + + // should.fail('should.fail', ''); + // } catch (e) { + // e.should.be.an.instanceOf(InvalidRequestError); + // e.message.should.equal('Missing parameter: `response_type`'); + // } + // }); + + // it('should throw an error if `response_type` is not `code`', () => { + // const model = { + // getAccessToken() {}, + // getClient() {}, + // saveAuthorizationCode() {}, + // }; + // const handler = new AuthorizeHandler({ + // authorizationCodeLifetime: 120, + // model, + // }); + // const request = new Request({ + // body: { response_type: 'foobar' }, + // headers: {}, + // method: {}, + // query: {}, + // }); + + // try { + // handler.getResponseType(request); + + // should.fail('should.fail', ''); + // } catch (e) { + // e.should.be.an.instanceOf(UnsupportedResponseTypeError); + // e.message.should.equal( + // 'Unsupported response type: `response_type` is not supported', + // ); + // } + // }); + + // describe('with `response_type` in the request body', () => { + // it('should return a response type', () => { + // const model = { + // getAccessToken() {}, + // getClient() {}, + // saveAuthorizationCode() {}, + // }; + // const handler = new AuthorizeHandler({ + // authorizationCodeLifetime: 120, + // model, + // }); + // const request = new Request({ + // body: { response_type: 'code' }, + // headers: {}, + // method: {}, + // query: {}, + // }); + // const ResponseType = handler.getResponseType(request); + + // ResponseType.should.equal(CodeResponseType); + // }); + // }); + + // describe('with `response_type` in the request query', () => { + // it('should return a response type', () => { + // const model = { + // getAccessToken() {}, + // getClient() {}, + // saveAuthorizationCode() {}, + // }; + // const handler = new AuthorizeHandler({ + // authorizationCodeLifetime: 120, + // model, + // }); + // const request = new Request({ + // body: {}, + // headers: {}, + // method: {}, + // query: { response_type: 'code' }, + // }); + // const ResponseType = handler.getResponseType(request); + + // ResponseType.should.equal(CodeResponseType); + // }); + // }); + // }); describe('buildSuccessRedirectUri()', () => { it('should return a redirect uri', () => { @@ -1512,6 +1511,7 @@ describe('AuthorizeHandler integration', () => { }); const redirectUri = handler.buildErrorRedirectUri( 'http://example.com/cb', + new CodeResponseType(), error, ); @@ -1535,6 +1535,7 @@ describe('AuthorizeHandler integration', () => { }); const redirectUri = handler.buildErrorRedirectUri( 'http://example.com/cb', + new CodeResponseType(), error, ); @@ -1560,7 +1561,7 @@ describe('AuthorizeHandler integration', () => { const response = new Response({ body: {}, headers: {} }); const uri = url.parse('http://example.com/cb', true); - handler.updateResponse(response, uri, 'foobar'); + handler.updateResponse(response, uri, new CodeResponseType(), 'foobar'); response .get('location') diff --git a/test/integration/handlers/token-handler.spec.ts b/test/integration/handlers/token-handler.spec.ts index 66de911d5..822a27e33 100755 --- a/test/integration/handlers/token-handler.spec.ts +++ b/test/integration/handlers/token-handler.spec.ts @@ -1496,7 +1496,7 @@ describe('TokenHandler integration', () => { describe('getRefreshTokenLifetime()', () => { it('should return the client access token lifetime', () => { - const client = { refreshTokenLifetime: 60 }; + const client: any = { refreshTokenLifetime: 60 }; const model = { getClient() { return client; @@ -1513,7 +1513,7 @@ describe('TokenHandler integration', () => { }); it('should return the default access token lifetime', () => { - const client = {}; + const client: any = {}; const model = { getClient() { return client; diff --git a/test/unit/grant-types/authorization-code-grant-type.spec.ts b/test/unit/grant-types/authorization-code-grant-type.spec.ts index b775a6603..b50044dc6 100755 --- a/test/unit/grant-types/authorization-code-grant-type.spec.ts +++ b/test/unit/grant-types/authorization-code-grant-type.spec.ts @@ -30,7 +30,7 @@ describe('AuthorizationCodeGrantType', () => { method: {}, query: {}, }); - const client = {}; + const client: any = {}; try { await handler.getAuthorizationCode(request, client); diff --git a/test/unit/handlers/authorize-handler.spec.ts b/test/unit/handlers/authorize-handler.spec.ts index 29d9eba36..20f5951c8 100755 --- a/test/unit/handlers/authorize-handler.spec.ts +++ b/test/unit/handlers/authorize-handler.spec.ts @@ -9,27 +9,27 @@ import { Response } from '../../../lib/response'; */ describe('AuthorizeHandler', () => { - describe('generateAuthorizationCode()', () => { - it('should call `model.generateAuthorizationCode()`', async () => { - const model = { - generateAuthorizationCode: sinon.stub().returns({}), - getAccessToken() {}, - getClient() {}, - saveAuthorizationCode() {}, - }; - const handler = new AuthorizeHandler({ - authorizationCodeLifetime: 120, - model, - }); - try { - await handler.generateAuthorizationCode({}, {}, {}); - model.generateAuthorizationCode.callCount.should.equal(1); - model.generateAuthorizationCode.firstCall.thisValue.should.equal(model); - } catch (error) { - should.fail('should.fail', ''); - } - }); - }); + // describe('generateAuthorizationCode()', () => { + // it('should call `model.generateAuthorizationCode()`', async () => { + // const model = { + // generateAuthorizationCode: sinon.stub().returns({}), + // getAccessToken() {}, + // getClient() {}, + // saveAuthorizationCode() {}, + // }; + // const handler = new AuthorizeHandler({ + // authorizationCodeLifetime: 120, + // model, + // }); + // try { + // await handler.generateAuthorizationCode({}, {}, {}); + // model.generateAuthorizationCode.callCount.should.equal(1); + // model.generateAuthorizationCode.firstCall.thisValue.should.equal(model); + // } catch (error) { + // should.fail('should.fail', ''); + // } + // }); + // }); describe('getClient()', () => { it('should call `model.getClient()`', async () => { @@ -100,40 +100,39 @@ describe('AuthorizeHandler', () => { }); describe('saveAuthorizationCode()', () => { - it('should call `model.saveAuthorizationCode()`', () => { - const model = { - getAccessToken() {}, - getClient() {}, - saveAuthorizationCode: sinon.stub().returns({}), - }; - const handler = new AuthorizeHandler({ - authorizationCodeLifetime: 120, - model, - }); - - return handler - .saveAuthorizationCode( - 'foo', - 'bar' as any, - 'qux', - 'biz' as any, - 'baz', - 'boz' as any, - ) - .then(() => { - model.saveAuthorizationCode.callCount.should.equal(1); - model.saveAuthorizationCode.firstCall.args.should.have.length(3); - model.saveAuthorizationCode.firstCall.args[0].should.eql({ - authorizationCode: 'foo', - expiresAt: 'bar', - redirectUri: 'baz', - scope: 'qux', - }); - model.saveAuthorizationCode.firstCall.args[1].should.equal('biz'); - model.saveAuthorizationCode.firstCall.args[2].should.equal('boz'); - model.saveAuthorizationCode.firstCall.thisValue.should.equal(model); - }) - .catch(() => should.fail('should.fail', '')); - }); + // it('should call `model.saveAuthorizationCode()`', () => { + // const model = { + // getAccessToken() {}, + // getClient() {}, + // saveAuthorizationCode: sinon.stub().returns({}), + // }; + // const handler = new AuthorizeHandler({ + // authorizationCodeLifetime: 120, + // model, + // }); + // return handler + // .saveAuthorizationCode( + // 'foo', + // 'bar' as any, + // 'qux', + // 'biz' as any, + // 'baz', + // 'boz' as any, + // ) + // .then(() => { + // model.saveAuthorizationCode.callCount.should.equal(1); + // model.saveAuthorizationCode.firstCall.args.should.have.length(3); + // model.saveAuthorizationCode.firstCall.args[0].should.eql({ + // authorizationCode: 'foo', + // expiresAt: 'bar', + // redirectUri: 'baz', + // scope: 'qux', + // }); + // model.saveAuthorizationCode.firstCall.args[1].should.equal('biz'); + // model.saveAuthorizationCode.firstCall.args[2].should.equal('boz'); + // model.saveAuthorizationCode.firstCall.thisValue.should.equal(model); + // }) + // .catch(() => should.fail('should.fail', '')); + // }); }); }); diff --git a/tslint.json b/tslint.json index f36322d63..db33c429d 100755 --- a/tslint.json +++ b/tslint.json @@ -15,6 +15,7 @@ "member-ordering": [false], "no-default-export": [false], "no-magic-numbers": [false], + "newline-per-chained-call": false, "no-unsafe-any": false, "object-literal-sort-keys": false, "ordered-imports": [false], From b02764344fb97cff357a7cb75cc99184848a5368 Mon Sep 17 00:00:00 2001 From: Ankit Date: Sun, 21 Apr 2019 22:41:12 +0530 Subject: [PATCH 04/30] added-.vscode --- .gitignore | 2 +- .vscode/README.md | 22 ++ .vscode/extensions.json | 11 + .vscode/launch.json | 23 ++ .vscode/recommended-settings.json | 39 ++++ index.ts | 28 ++- lib/constants/common.ts | 1 + lib/constants/index.ts | 1 + lib/grant-types/abstract-grant-type.ts | 5 +- lib/handlers/authenticate-handler.ts | 2 +- lib/handlers/authorize-handler.ts | 5 +- lib/models/token-model.ts | 5 +- lib/response-types/code-response-type.ts | 3 +- lib/server.ts | 36 +-- package-lock.json | 277 +++++++++++++++++++++++ package.json | 15 +- scripts/build-prod.js | 3 - 17 files changed, 423 insertions(+), 55 deletions(-) create mode 100644 .vscode/README.md create mode 100644 .vscode/extensions.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/recommended-settings.json create mode 100644 lib/constants/common.ts create mode 100644 lib/constants/index.ts diff --git a/.gitignore b/.gitignore index 51563fc34..f084b5708 100755 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ # IDE /.idea /.awcache -/.vscode +/.vscode/*.code-workspace # misc npm-debug.log diff --git a/.vscode/README.md b/.vscode/README.md new file mode 100644 index 000000000..45c60edfb --- /dev/null +++ b/.vscode/README.md @@ -0,0 +1,22 @@ +# VSCode Configuration + +This folder contains opt-in [Workspace Settings](https://code.visualstudio.com/docs/getstarted/settings) and [Extension Recommendations](https://code.visualstudio.com/docs/editor/extension-gallery#_workspace-recommended-extensions) that the our team recommends using when working on this repository. + +## Usage + +To use the recommended settings follow the steps below: + +- copy `.vscode/recommended-settings.json` to `.vscode/settings.json` +- restart the editor + +If you already have your custom workspace settings you should instead manually merge the file content. + +This isn't an automatic process so you will need to repeat it when settings are updated. + +To see the recommended extensions select "Extensions: Show Recommended Extensions" in the [Command Palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette). + +## Editing `.vscode/recommended-settings.json` + +If you wish to add extra configuration items please keep in mind any settings you add here will be used by many users. + +Try to keep these settings to things that help facilitate the development process and avoid altering the user workflow whenever possible. diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000..7fac8f753 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,11 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + + // List of extensions which should be recommended for users of this workspace. + "recommendations": [ + "editorconfig.editorconfig", + "esbenp.prettier-vscode", + "ms-vscode.vscode-typescript-tslint-plugin" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..51fea6b85 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,23 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Mocha Tests", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", + "args": [ + "--require", + "ts-node/register", + "-u", + "tdd", + "--timeout", + "999999", + "--colors", + "--recursive", + "${workspaceFolder}/test/**/*.spec.ts" + ], + "internalConsoleOptions": "openOnSessionStart" + } + ] +} diff --git a/.vscode/recommended-settings.json b/.vscode/recommended-settings.json new file mode 100644 index 000000000..3fa8122cd --- /dev/null +++ b/.vscode/recommended-settings.json @@ -0,0 +1,39 @@ +{ + "files.exclude": { + "**/.git": true, + "**/.DS_Store": true, + "**/node_modules": true, + "dist": true + }, + "files.watcherExclude": { + "**/.git/**": true, + "**/node_modules/**": true, + "**/dist/**": true + }, + "search.exclude": { + "**/node_modules": true, + "**/dist": true + }, + "editor.formatOnSave": false, + "typescript.format.enable": false, + "editor.formatOnPaste": false, + "[typescript]": { + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": true, + "source.fixAll.tslint": true + } + }, + "[json]": { + "editor.formatOnSave": true + }, + "[jsonc]": { + "editor.formatOnSave": true + }, + "[javascript]": { + "editor.formatOnSave": true + }, + "prettier.singleQuote": true, + "prettier.trailingComma": "all", + "prettier.printWidth": 80 +} diff --git a/index.ts b/index.ts index f12e05037..b7388d03a 100755 --- a/index.ts +++ b/index.ts @@ -1,2 +1,26 @@ -import { OAuth2Server } from './lib/server'; -export default OAuth2Server; +export { + AccessDeniedError, + InsufficientScopeError, + InvalidArgumentError, + InvalidClientError, + InvalidGrantError, + InvalidRequestError, + InvalidScopeError, + InvalidTokenError, + OAuthError, + ServerError, + UnauthorizedClientError, + UnauthorizedRequestError, + UnsupportedGrantTypeError, + UnsupportedResponseTypeError, +} from './lib/errors'; +export { AbstractGrantType } from './lib/grant-types'; +export { + AuthenticateHandler, + AuthorizeHandler, + RevokeHandler, + TokenHandler, +} from './lib/handlers'; +export { Request } from './lib/request'; +export { Response } from './lib/response'; +export { OAuth2Server } from './lib/server'; diff --git a/lib/constants/common.ts b/lib/constants/common.ts new file mode 100644 index 000000000..934dcebdb --- /dev/null +++ b/lib/constants/common.ts @@ -0,0 +1 @@ +export const MS_IN_S = 1_000; diff --git a/lib/constants/index.ts b/lib/constants/index.ts new file mode 100644 index 000000000..d0b932366 --- /dev/null +++ b/lib/constants/index.ts @@ -0,0 +1 @@ +export * from './common'; diff --git a/lib/grant-types/abstract-grant-type.ts b/lib/grant-types/abstract-grant-type.ts index 9cebd1f84..6c995062d 100755 --- a/lib/grant-types/abstract-grant-type.ts +++ b/lib/grant-types/abstract-grant-type.ts @@ -1,3 +1,4 @@ +import { MS_IN_S } from '../constants'; import { InvalidArgumentError, InvalidScopeError } from '../errors'; import { Client, Model, User } from '../interfaces'; import { Request } from '../request'; @@ -60,7 +61,7 @@ export class AbstractGrantType { */ getAccessTokenExpiresAt() { - return new Date(Date.now() + this.accessTokenLifetime * 1000); + return new Date(Date.now() + this.accessTokenLifetime * MS_IN_S); } /** @@ -68,7 +69,7 @@ export class AbstractGrantType { */ getRefreshTokenExpiresAt() { - return new Date(Date.now() + this.refreshTokenLifetime * 1000); + return new Date(Date.now() + this.refreshTokenLifetime * MS_IN_S); } /** diff --git a/lib/handlers/authenticate-handler.ts b/lib/handlers/authenticate-handler.ts index 60b56e050..b17c34825 100755 --- a/lib/handlers/authenticate-handler.ts +++ b/lib/handlers/authenticate-handler.ts @@ -257,7 +257,7 @@ export class AuthenticateHandler { * Update response. */ - updateResponse(response: Response, accessToken) { + updateResponse(response: Response, accessToken: Token) { if (this.scope && this.addAcceptedScopesHeader) { response.set('X-Accepted-OAuth-Scopes', this.scope); } diff --git a/lib/handlers/authorize-handler.ts b/lib/handlers/authorize-handler.ts index e7256332d..1e07bbfea 100755 --- a/lib/handlers/authorize-handler.ts +++ b/lib/handlers/authorize-handler.ts @@ -87,7 +87,7 @@ export class AuthorizeHandler { // this.model.request = request; const fns = [this.getClient(request), this.getUser(request, response)]; - // try { + const [client, user] = await Promise.all(fns); let scope: string; @@ -125,7 +125,6 @@ export class AuthorizeHandler { throw e; } - // } catch (error) {} } /** @@ -228,7 +227,7 @@ export class AuthorizeHandler { * Get state from the request. */ - getState(request) { + getState(request: Request) { const state = request.body.state || request.query.state; if (!this.allowEmptyState && !state) { diff --git a/lib/models/token-model.ts b/lib/models/token-model.ts index a8ded4015..adc4fac0a 100755 --- a/lib/models/token-model.ts +++ b/lib/models/token-model.ts @@ -1,3 +1,4 @@ +import { MS_IN_S } from '../constants'; import { InvalidArgumentError } from '../errors/invalid-argument-error'; import { Client } from '../interfaces/client.interface'; import { Token } from '../interfaces/token.interface'; @@ -71,10 +72,10 @@ export class TokenModel implements Token { } } } - const msInS = 1000; + if (this.accessTokenExpiresAt) { this.accessTokenLifetime = Math.floor( - (this.accessTokenExpiresAt.getTime() - new Date().getTime()) / msInS, + (this.accessTokenExpiresAt.getTime() - new Date().getTime()) / MS_IN_S, ); } } diff --git a/lib/response-types/code-response-type.ts b/lib/response-types/code-response-type.ts index 26192be97..080ec9a0f 100755 --- a/lib/response-types/code-response-type.ts +++ b/lib/response-types/code-response-type.ts @@ -1,3 +1,4 @@ +import { MS_IN_S } from '../constants'; import { InvalidArgumentError } from '../errors'; import { AuthorizationCode, Client, Model, User } from '../interfaces'; import { Request } from '../request'; @@ -82,7 +83,7 @@ export class CodeResponseType { getAuthorizationCodeExpiresAt(client: Client) { const authorizationCodeLifetime = this.getAuthorizationCodeLifetime(client); - return new Date(Date.now() + authorizationCodeLifetime * 1000); + return new Date(Date.now() + authorizationCodeLifetime * MS_IN_S); } /** diff --git a/lib/server.ts b/lib/server.ts index ba5672f0f..be188686a 100755 --- a/lib/server.ts +++ b/lib/server.ts @@ -1,20 +1,4 @@ -import { - AccessDeniedError, - InsufficientScopeError, - InvalidArgumentError, - InvalidClientError, - InvalidGrantError, - InvalidRequestError, - InvalidScopeError, - InvalidTokenError, - OAuthError, - ServerError, - UnauthorizedClientError, - UnauthorizedRequestError, - UnsupportedGrantTypeError, - UnsupportedResponseTypeError, -} from './errors'; -import { AbstractGrantType } from './grant-types'; +import { InvalidArgumentError } from './errors'; import { AuthenticateHandler, AuthorizeHandler, @@ -112,22 +96,4 @@ export class OAuth2Server { return new RevokeHandler(opt).handle(request, response); } - - static Request = Request; - static Response = Response; - static AbstractGrantType = AbstractGrantType; - static AccessDeniedError = AccessDeniedError; - static InsufficientScopeError = InsufficientScopeError; - static InvalidArgumentError = InvalidArgumentError; - static InvalidClientError = InvalidClientError; - static InvalidGrantError = InvalidGrantError; - static InvalidRequestError = InvalidRequestError; - static InvalidScopeError = InvalidScopeError; - static InvalidTokenError = InvalidTokenError; - static OAuthError = OAuthError; - static ServerError = ServerError; - static UnauthorizedClientError = UnauthorizedClientError; - static UnauthorizedRequestError = UnauthorizedRequestError; - static UnsupportedGrantTypeError = UnsupportedGrantTypeError; - static UnsupportedResponseTypeError = UnsupportedResponseTypeError; } diff --git a/package-lock.json b/package-lock.json index d17e7b23f..0199db92a 100755 --- a/package-lock.json +++ b/package-lock.json @@ -120,6 +120,15 @@ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, "arg": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.0.tgz", @@ -135,12 +144,30 @@ "sprintf-js": "~1.0.2" } }, + "array-filter": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz", + "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=", + "dev": true + }, "array-from": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", "dev": true }, + "array-map": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz", + "integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=", + "dev": true + }, + "array-reduce": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz", + "integrity": "sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=", + "dev": true + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -339,6 +366,15 @@ "once": "^1.4.0" } }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, "es-abstract": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", @@ -364,6 +400,12 @@ "is-symbol": "^1.0.2" } }, + "es6-object-assign": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", + "integrity": "sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw=", + "dev": true + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -456,6 +498,12 @@ "path-is-absolute": "^1.0.0" } }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, "growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", @@ -489,6 +537,12 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, + "hosted-git-info": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -505,12 +559,24 @@ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, + "interpret": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", + "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", + "dev": true + }, "invert-kv": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", "dev": true }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, "is-buffer": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", @@ -587,6 +653,18 @@ "esprima": "^4.0.0" } }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, "just-extend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz", @@ -602,6 +680,18 @@ "invert-kv": "^2.0.0" } }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, "locate-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", @@ -663,6 +753,12 @@ "p-is-promise": "^2.0.0" } }, + "memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", + "dev": true + }, "mime-db": { "version": "1.38.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", @@ -807,6 +903,35 @@ "semver": "^5.7.0" } }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + } + }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", @@ -912,6 +1037,16 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", @@ -945,6 +1080,27 @@ "isarray": "0.0.1" } }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pidtree": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.0.tgz", + "integrity": "sha512-9CT4NFlDcosssyg8KVFltgokyKZIFjoBxw8CTGy+5F38Y1eQWrt8tRayiUOXE+zVKQnYu5BR8JjCtvK3BcnBhg==", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -955,6 +1111,26 @@ "once": "^1.3.1" } }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -1008,6 +1184,29 @@ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, + "shell-quote": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz", + "integrity": "sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c=", + "dev": true, + "requires": { + "array-filter": "~0.0.0", + "array-map": "~0.0.0", + "array-reduce": "~0.0.0", + "jsonify": "~0.0.0" + } + }, + "shelljs": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.3.tgz", + "integrity": "sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A==", + "dev": true, + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, "should": { "version": "13.2.3", "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", @@ -1062,6 +1261,25 @@ "integrity": "sha1-yYzaN0qmsZDfi6h8mInCtNtiAGM=", "dev": true }, + "shx": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.2.tgz", + "integrity": "sha512-aS0mWtW3T2sHAenrSrip2XGv39O9dXIFUqxAEWHEOS1ePtGIBavdPJY1kE2IHl14V/4iCbUiNDPGdyYTtmhSoA==", + "dev": true, + "requires": { + "es6-object-assign": "^1.0.3", + "minimist": "^1.2.0", + "shelljs": "^0.8.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", @@ -1110,6 +1328,38 @@ "source-map": "^0.6.0" } }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", + "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==", + "dev": true + }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -1148,6 +1398,17 @@ } } }, + "string.prototype.padend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.0.0.tgz", + "integrity": "sha1-86rvfBcZ8XDF6rHDK/eA2W4h8vA=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.4.3", + "function-bind": "^1.0.2" + } + }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -1157,6 +1418,12 @@ "ansi-regex": "^2.0.0" } }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", @@ -1232,6 +1499,16 @@ "integrity": "sha512-FFgHdPt4T/duxx6Ndf7hwgMZZjZpB+U0nMNGVCYPq0rEzWKjEDobm4J6yb3CS7naZ0yURFqdw9Gwc7UOh/P9oQ==", "dev": true }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", diff --git a/package.json b/package.json index c45637a36..30e01b7eb 100755 --- a/package.json +++ b/package.json @@ -43,9 +43,6 @@ "tslib": "^1.9.3" }, "devDependencies": { - "mocha": "^6.1.3", - "should": "^13.2.3", - "sinon": "^7.3.1", "@types/basic-auth": "^1.1.2", "@types/lodash": "^4.14.123", "@types/mocha": "^5.2.6", @@ -53,6 +50,11 @@ "@types/sinon": "^7.0.11", "@types/statuses": "^1.5.0", "@types/type-is": "^1.6.2", + "mocha": "^6.1.3", + "npm-run-all": "^4.1.5", + "should": "^13.2.3", + "shx": "^0.3.2", + "sinon": "^7.3.1", "ts-node": "^8.1.0", "tslint": "^5.16.0", "typescript": "^3.4.3" @@ -65,8 +67,11 @@ "lint": "tslint -p tsconfig.build.json -c tslint.json", "lint:all": "tslint -p tsconfig.json -c tslint.json", "pretest": "npx jshint --config ./.jshintrc lib test", - "build": "npx tsc -p tsconfig.build.json", - "build:prod": "npx tsc -p tsconfig.build.json && node ./scripts/build-prod.js", + "build": "npx tsc -p tsconfig.json", + "build:clean": "npx shx rm -rf ./dist", + "build:prod": "npx npm-run-all build:clean build:prod:*", + "build:prod:src": "npx tsc -p tsconfig.build.json", + "build:prod:package": "node ./scripts/build-prod.js", "test": "NODE_ENV=test npx mocha 'test/**/*_test.js'", "test2": "npx mocha 'test/**/*.spec.ts' --config 'test/.mocharc.jsonc'", "test-debug": "NODE_ENV=test npx mocha --inspect --debug-brk 'test/**/*_test.js'" diff --git a/scripts/build-prod.js b/scripts/build-prod.js index bab2b0a7e..528b55a72 100644 --- a/scripts/build-prod.js +++ b/scripts/build-prod.js @@ -3,9 +3,6 @@ const packageJson = require('../package.json'); delete packageJson.scripts; delete packageJson.devDependencies; -packageJson.contributors = packageJson.contributors.map( - ({ name, email }) => name + (email ? ` <${email}>` : ''), -); fs.writeFileSync( __dirname + '/../dist/package.json', JSON.stringify(packageJson, null, 2), From 2cd5e080d8536d41c42b3e0f09f8f934a51ddad8 Mon Sep 17 00:00:00 2001 From: Ankit Date: Mon, 22 Apr 2019 19:31:42 +0530 Subject: [PATCH 05/30] teslint-fixes --- lib/grant-types/implicit-grant-type.ts | 2 +- lib/grant-types/refresh-token-grant-type.ts | 4 ++-- lib/handlers/authenticate-handler.ts | 4 ++-- lib/models/token-model.ts | 3 ++- lib/request.ts | 7 ++++--- lib/response-types/code-response-type.ts | 2 +- 6 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/grant-types/implicit-grant-type.ts b/lib/grant-types/implicit-grant-type.ts index 9f18fc8a1..c4d581740 100644 --- a/lib/grant-types/implicit-grant-type.ts +++ b/lib/grant-types/implicit-grant-type.ts @@ -32,7 +32,7 @@ export class ImplicitGrantType extends AbstractGrantType { * Handle implicit token grant. */ - handle(request: Request, client: Client) { + async handle(request: Request, client: Client) { if (!request) { throw new InvalidArgumentError('Missing parameter: `request`'); } diff --git a/lib/grant-types/refresh-token-grant-type.ts b/lib/grant-types/refresh-token-grant-type.ts index 2137d7eb4..d2ea685a6 100755 --- a/lib/grant-types/refresh-token-grant-type.ts +++ b/lib/grant-types/refresh-token-grant-type.ts @@ -118,7 +118,7 @@ export class RefreshTokenGrantType extends AbstractGrantType { */ async revokeToken(token: RefreshToken) { - if (this.alwaysIssueNewRefreshToken === false) { + if (!this.alwaysIssueNewRefreshToken) { return token; } @@ -155,7 +155,7 @@ export class RefreshTokenGrantType extends AbstractGrantType { scope, }; - if (this.alwaysIssueNewRefreshToken !== false) { + if (this.alwaysIssueNewRefreshToken) { token.refreshToken = refreshToken; token.refreshTokenExpiresAt = refreshTokenExpiresAt; } diff --git a/lib/handlers/authenticate-handler.ts b/lib/handlers/authenticate-handler.ts index b17c34825..9da007e2e 100755 --- a/lib/handlers/authenticate-handler.ts +++ b/lib/handlers/authenticate-handler.ts @@ -73,11 +73,11 @@ export class AuthenticateHandler { try { let token = await this.getTokenFromRequest(request); token = await this.getAccessToken(token); - await this.validateAccessToken(token); + this.validateAccessToken(token); if (this.scope) { await this.verifyScope(token); } - await this.updateResponse(response, token); + this.updateResponse(response, token); return token; } catch (e) { diff --git a/lib/models/token-model.ts b/lib/models/token-model.ts index adc4fac0a..766762181 100755 --- a/lib/models/token-model.ts +++ b/lib/models/token-model.ts @@ -3,6 +3,7 @@ import { InvalidArgumentError } from '../errors/invalid-argument-error'; import { Client } from '../interfaces/client.interface'; import { Token } from '../interfaces/token.interface'; import { User } from '../interfaces/user.interface'; +import { hasOwnProperty } from '../utils/fn'; const modelAttributes = [ 'accessToken', @@ -67,7 +68,7 @@ export class TokenModel implements Token { this.customAttributes = {}; for (const key in data) { - if (data.hasOwnProperty(key) && modelAttributes.indexOf(key) < 0) { + if (hasOwnProperty(key, data) && modelAttributes.indexOf(key) < 0) { this.customAttributes[key] = data[key]; } } diff --git a/lib/request.ts b/lib/request.ts index 9163ca5ad..6e70938ec 100755 --- a/lib/request.ts +++ b/lib/request.ts @@ -1,5 +1,6 @@ import * as typeis from 'type-is'; import { InvalidArgumentError } from './errors'; +import { hasOwnProperty } from './utils/fn'; export class Request { body: any; @@ -26,14 +27,14 @@ export class Request { // Store the headers in lower case. for (const field in options.headers) { - if (options.headers.hasOwnProperty(field)) { + if (hasOwnProperty(options.headers, field)) { this.headers[field.toLowerCase()] = options.headers[field]; } } // Store additional properties of the request object passed in for (const property in options) { - if (options.hasOwnProperty(property) && !this[property]) { + if (hasOwnProperty(options, property) && !this[property]) { this[property] = options[property]; } } @@ -43,7 +44,7 @@ export class Request { * Get a request header. */ - get(field) { + get(field: string) { return this.headers[field.toLowerCase()]; } diff --git a/lib/response-types/code-response-type.ts b/lib/response-types/code-response-type.ts index 080ec9a0f..96215ae22 100755 --- a/lib/response-types/code-response-type.ts +++ b/lib/response-types/code-response-type.ts @@ -98,7 +98,7 @@ export class CodeResponseType { * Save authorization code. */ - saveAuthorizationCode( + async saveAuthorizationCode( authorizationCode: string, expiresAt: Date, scope: string, From a4f99a170caa78f3d708e18949a3024ee17b74cb Mon Sep 17 00:00:00 2001 From: Ankit Date: Mon, 22 Apr 2019 19:45:10 +0530 Subject: [PATCH 06/30] fixed-gitignore --- .gitignore | 17 +++++------------ README.md | 8 ++++---- 2 files changed, 9 insertions(+), 16 deletions(-) mode change 100755 => 100644 README.md diff --git a/.gitignore b/.gitignore index f084b5708..e1bb9b2c9 100755 --- a/.gitignore +++ b/.gitignore @@ -6,19 +6,12 @@ # misc npm-debug.log - -# example -/quick-start - -# tests -# /test -/coverage -/.nyc_output - -# dist +# folders /dist /node_modules +docs/_build/ +__pycache__/ +# files .DS_Store -# conf -ormconfig.json +*.pyc diff --git a/README.md b/README.md old mode 100755 new mode 100644 index b2a04b185..33a85901d --- a/README.md +++ b/README.md @@ -20,9 +20,9 @@ The *oauth2-server* module is framework-agnostic but there are several officiall ## Features -- Supports `authorization_code`, `client_credentials`, `refresh_token` and `password` grant, as well as *extension grants*, with scopes. +- Supports `authorization_code`, `client_credentials`, `refresh_token`, `implicit` and `password` grant, as well as *extension grants*, with scopes. - Can be used with *promises*, *Node-style callbacks*, *ES6 generators* and *async*/*await* (using [Babel](https://babeljs.io)). -- Fully [RFC 6749](https://tools.ietf.org/html/rfc6749.html) and [RFC 6750](https://tools.ietf.org/html/rfc6749.html) compliant. +- Fully [RFC 6749](https://tools.ietf.org/html/rfc6749.html) and [RFC 6750](https://tools.ietf.org/html/rfc6750.html) compliant. - Implicitly supports any form of storage, e.g. *PostgreSQL*, *MySQL*, *MongoDB*, *Redis*, etc. - Complete [test suite](https://github.com/oauthjs/node-oauth2-server/tree/master/test). @@ -63,6 +63,6 @@ npm test [travis-url]: https://travis-ci.org/oauthjs/node-oauth2-server [license-image]: https://img.shields.io/badge/license-MIT-blue.svg [license-url]: https://raw.githubusercontent.com/oauthjs/node-oauth2-server/master/LICENSE -[slack-image]: https://img.shields.io/badge/slack-join-E01563.svg -[slack-url]: https://oauthjs.slack.com +[slack-image]: https://slack.oauthjs.org/badge.svg +[slack-url]: https://slack.oauthjs.org From cac41dd269a79797808dbcf1852e9837d7a1a79b Mon Sep 17 00:00:00 2001 From: Ankit Date: Tue, 23 Apr 2019 22:40:20 +0530 Subject: [PATCH 07/30] added-docs --- TODO | 5 +++++ lib/grant-types/implicit-grant-type.ts | 3 +-- package-lock.json | 2 +- package.json | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 TODO diff --git a/TODO b/TODO new file mode 100644 index 000000000..f781a9156 --- /dev/null +++ b/TODO @@ -0,0 +1,5 @@ + +Todo: + ✔ Add a todo + ✔ A Basic Rewrite of library in Typescript + ☐ A Basic Rewrite of tests in Typescript @started diff --git a/lib/grant-types/implicit-grant-type.ts b/lib/grant-types/implicit-grant-type.ts index c4d581740..dba456d97 100644 --- a/lib/grant-types/implicit-grant-type.ts +++ b/lib/grant-types/implicit-grant-type.ts @@ -1,7 +1,6 @@ import { AbstractGrantType } from '.'; import { InvalidArgumentError } from '../errors'; -import { Client, User } from '../interfaces'; -import { Token } from '../interfaces/token.interface'; +import { Client, Token, User } from '../interfaces'; import { Request } from '../request'; export class ImplicitGrantType extends AbstractGrantType { diff --git a/package-lock.json b/package-lock.json index 0199db92a..0b9b9543b 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "oauth2-server", - "version": "3.0.1", + "version": "5.0.0-dev.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 30e01b7eb..63558decd 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "oauth2-server", "description": "Complete, framework-agnostic, compliant and well tested module for implementing an OAuth2 Server in node.js", - "version": "3.0.1", + "version": "5.0.0-dev.0", "keywords": [ "oauth", "oauth2" From 8b31697295e5eed0e814567ddecf0d5a14fac34b Mon Sep 17 00:00:00 2001 From: ankit Date: Thu, 25 Apr 2019 00:22:57 +0700 Subject: [PATCH 08/30] expose-interfaces --- .gitignore | 35 ++++++------ index.ts | 30 +++------- lib/errors/unauthorized-client-error.ts | 5 +- lib/grant-types/abstract-grant-type.ts | 4 +- .../authorization-code-grant-type.ts | 4 +- lib/handlers/authenticate-handler.ts | 12 ++-- lib/handlers/authorize-handler.ts | 30 +++++----- lib/handlers/revoke-handler.ts | 12 ++-- lib/handlers/token-handler.ts | 16 +++--- lib/response-types/code-response-type.ts | 4 +- lib/response-types/token-response-type.ts | 8 +-- lib/token-types/index.ts | 2 + tslint.json | 55 ++++++++++--------- 13 files changed, 104 insertions(+), 113 deletions(-) create mode 100644 lib/token-types/index.ts diff --git a/.gitignore b/.gitignore index e1bb9b2c9..24d4f1ebe 100755 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,18 @@ - -# IDE -/.idea -/.awcache -/.vscode/*.code-workspace - -# misc -npm-debug.log -# folders -/dist -/node_modules -docs/_build/ -__pycache__/ - -# files -.DS_Store -*.pyc + +# IDE +/.idea +/.awcache +/.vscode/*.code-workspace + +# misc +npm-debug.log +# folders +/dist +/node_modules +docs/_build/ +__pycache__/ + +# files +.DS_Store +*.pyc +/aio \ No newline at end of file diff --git a/index.ts b/index.ts index b7388d03a..94825ea61 100755 --- a/index.ts +++ b/index.ts @@ -1,26 +1,10 @@ -export { - AccessDeniedError, - InsufficientScopeError, - InvalidArgumentError, - InvalidClientError, - InvalidGrantError, - InvalidRequestError, - InvalidScopeError, - InvalidTokenError, - OAuthError, - ServerError, - UnauthorizedClientError, - UnauthorizedRequestError, - UnsupportedGrantTypeError, - UnsupportedResponseTypeError, -} from './lib/errors'; -export { AbstractGrantType } from './lib/grant-types'; -export { - AuthenticateHandler, - AuthorizeHandler, - RevokeHandler, - TokenHandler, -} from './lib/handlers'; +export * from './lib/errors'; +export * from './lib/grant-types'; +export * from './lib/handlers'; +export * from './lib/interfaces'; +export * from './lib/response-types'; +export * from './lib/token-types'; export { Request } from './lib/request'; export { Response } from './lib/response'; export { OAuth2Server } from './lib/server'; +export * from './lib/validator/is'; \ No newline at end of file diff --git a/lib/errors/unauthorized-client-error.ts b/lib/errors/unauthorized-client-error.ts index e0e5f6a18..87d7167b7 100755 --- a/lib/errors/unauthorized-client-error.ts +++ b/lib/errors/unauthorized-client-error.ts @@ -9,7 +9,10 @@ import { OAuthError } from './oauth-error'; */ export class UnauthorizedClientError extends OAuthError { - constructor(message?: string | Error, properties?: any) { + constructor( + message?: string | Error, + properties?: { code?: number; message?: string }, + ) { super(message, { code: 400, name: 'unauthorized_client', ...properties }); } } diff --git a/lib/grant-types/abstract-grant-type.ts b/lib/grant-types/abstract-grant-type.ts index 6c995062d..8839d6b88 100755 --- a/lib/grant-types/abstract-grant-type.ts +++ b/lib/grant-types/abstract-grant-type.ts @@ -76,13 +76,13 @@ export class AbstractGrantType { * Get scope from the request body. */ - getScope = (request: Request) => { + getScope(request: Request) { if (!is.nqschar(request.body.scope)) { throw new InvalidArgumentError('Invalid parameter: `scope`'); } return request.body.scope; - }; + } /** * Validate requested scope. diff --git a/lib/grant-types/authorization-code-grant-type.ts b/lib/grant-types/authorization-code-grant-type.ts index a7ac73449..19f21d4ee 100755 --- a/lib/grant-types/authorization-code-grant-type.ts +++ b/lib/grant-types/authorization-code-grant-type.ts @@ -131,7 +131,7 @@ export class AuthorizationCodeGrantType extends AbstractGrantType { * @see https://tools.ietf.org/html/rfc6749#section-4.1.3 */ - validateRedirectUri = (request: Request, code) => { + validateRedirectUri(request: Request, code) { if (!code.redirectUri) { return; } @@ -149,7 +149,7 @@ export class AuthorizationCodeGrantType extends AbstractGrantType { 'Invalid request: `redirect_uri` is invalid', ); } - }; + } /** * Revoke the authorization code. diff --git a/lib/handlers/authenticate-handler.ts b/lib/handlers/authenticate-handler.ts index 9da007e2e..d62a1a699 100755 --- a/lib/handlers/authenticate-handler.ts +++ b/lib/handlers/authenticate-handler.ts @@ -139,7 +139,7 @@ export class AuthenticateHandler { * @see http://tools.ietf.org/html/rfc6750#section-2.1 */ - getTokenFromRequestHeader = (request: Request) => { + getTokenFromRequestHeader(request: Request) { const token = request.get('Authorization'); const matches = token.match(/Bearer\s(\S+)/); @@ -150,7 +150,7 @@ export class AuthenticateHandler { } return matches[1]; - }; + } /** * Get the token from the request query. @@ -185,7 +185,7 @@ export class AuthenticateHandler { * @see http://tools.ietf.org/html/rfc6750#section-2.2 */ - getTokenFromRequestBody = (request: Request) => { + getTokenFromRequestBody(request: Request) { if (request.method === 'GET') { throw new InvalidRequestError( 'Invalid request: token may not be passed in the body when using the GET verb', @@ -199,7 +199,7 @@ export class AuthenticateHandler { } return request.body.access_token; - }; + } /** * Get the access token from the model. @@ -224,7 +224,7 @@ export class AuthenticateHandler { * Validate access token. */ - validateAccessToken = (accessToken: Token) => { + validateAccessToken(accessToken: Token) { if (!(accessToken.accessTokenExpiresAt instanceof Date)) { throw new ServerError( 'Server error: `accessTokenExpiresAt` must be a Date instance', @@ -236,7 +236,7 @@ export class AuthenticateHandler { } return accessToken; - }; + } /** * Verify scope. diff --git a/lib/handlers/authorize-handler.ts b/lib/handlers/authorize-handler.ts index 1e07bbfea..c3eee1444 100755 --- a/lib/handlers/authorize-handler.ts +++ b/lib/handlers/authorize-handler.ts @@ -213,7 +213,7 @@ export class AuthorizeHandler { * Get scope from the request. */ - getScope = (request: Request) => { + getScope(request: Request) { const scope = request.body.scope || request.query.scope; if (!is.nqschar(scope)) { @@ -221,7 +221,7 @@ export class AuthorizeHandler { } return scope; - }; + } /** * Get state from the request. @@ -266,19 +266,19 @@ export class AuthorizeHandler { * Get redirect URI. */ - getRedirectUri = (request: Request, client: Client) => { + getRedirectUri(request: Request, client: Client) { return ( request.body.redirect_uri || request.query.redirect_uri || client.redirectUris[0] ); - }; + } /** * Get response type. */ - getResponseType = (request: Request, client: Client) => { + getResponseType(request: Request, client: Client) { const responseType = request.body.response_type || request.query.response_type; @@ -302,30 +302,30 @@ export class AuthorizeHandler { } return responseTypes[responseType]; - }; + } /** * Build a successful response that redirects the user-agent to the client-provided url. */ - buildSuccessRedirectUri = ( + buildSuccessRedirectUri( redirectUri: string, responseType: CodeResponseType | TokenResponseType, - ) => { + ) { const uri = url.parse(redirectUri); return responseType.buildRedirectUri(uri); - }; + } /** * Build an error response that redirects the user-agent to the client-provided url. */ - buildErrorRedirectUri = ( + buildErrorRedirectUri( redirectUri: any, responseType: CodeResponseType | TokenResponseType, error: Error, - ) => { + ) { let uri = url.parse(redirectUri, true); if (responseType) { @@ -349,18 +349,18 @@ export class AuthorizeHandler { } return uri; - }; + } /** * Update response with the redirect uri and the state parameter, if available. */ - updateResponse = ( + updateResponse( response: Response, redirectUri: any, responseType: CodeResponseType | TokenResponseType, state: any, - ) => { + ) { if (responseType && state) { // tslint:disable-next-line:no-parameter-reassignment redirectUri = responseType.setRedirectUriParam( @@ -374,5 +374,5 @@ export class AuthorizeHandler { } response.redirect(url.format(redirectUri)); - }; + } } diff --git a/lib/handlers/revoke-handler.ts b/lib/handlers/revoke-handler.ts index 3026a97bc..6d0dcab98 100644 --- a/lib/handlers/revoke-handler.ts +++ b/lib/handlers/revoke-handler.ts @@ -185,7 +185,7 @@ export class RevokeHandler { * @see https://tools.ietf.org/html/rfc6749#section-2.3.1 */ - getClientCredentials = (request: Request) => { + getClientCredentials(request: Request) { const credentials = auth(request as any); if (credentials) { @@ -202,7 +202,7 @@ export class RevokeHandler { throw new InvalidClientError( 'Invalid client: cannot retrieve client credentials', ); - }; + } /** * Get the token from the body. @@ -210,7 +210,7 @@ export class RevokeHandler { * @see https://tools.ietf.org/html/rfc7009#section-2.1 */ - getTokenFromRequest = (request: Request) => { + getTokenFromRequest(request: Request) { const bodyToken = request.body.token; if (!bodyToken) { @@ -218,7 +218,7 @@ export class RevokeHandler { } return bodyToken; - }; + } /** * Get refresh token. @@ -327,12 +327,12 @@ export class RevokeHandler { * Update response when an error is thrown. */ - updateErrorResponse = (response: Response, error: OAuthError) => { + updateErrorResponse(response: Response, error: OAuthError) { response.body = { error: error.name, error_description: error.message, }; response.status = error.code; - }; + } } diff --git a/lib/handlers/token-handler.ts b/lib/handlers/token-handler.ts index dcf8fe072..71463d3e7 100755 --- a/lib/handlers/token-handler.ts +++ b/lib/handlers/token-handler.ts @@ -278,7 +278,7 @@ export class TokenHandler { * Get token type. */ - getTokenType = (model: any) => { + getTokenType(model: any) { return new BearerTokenType( model.accessToken, model.accessTokenLifetime, @@ -286,36 +286,36 @@ export class TokenHandler { model.scope, model.customAttributes, ); - }; + } /** * Update response when a token is generated. */ - updateSuccessResponse = (response: Response, tokenType: BearerTokenType) => { + updateSuccessResponse(response: Response, tokenType: BearerTokenType) { response.body = tokenType.valueOf(); response.set('Cache-Control', 'no-store'); response.set('Pragma', 'no-cache'); - }; + } /** * Update response when an error is thrown. */ - updateErrorResponse = (response: Response, error: OAuthError) => { + updateErrorResponse(response: Response, error: OAuthError) { response.body = { error: error.name, error_description: error.message, }; response.status = error.code; - }; + } /** * Given a grant type, check if client authentication is required */ - isClientAuthenticationRequired = (grantType: string) => { + isClientAuthenticationRequired(grantType: string) { if (Object.keys(this.requireClientAuthentication).length > 0) { return typeof this.requireClientAuthentication[grantType] !== 'undefined' ? this.requireClientAuthentication[grantType] @@ -323,5 +323,5 @@ export class TokenHandler { } return true; - }; + } } diff --git a/lib/response-types/code-response-type.ts b/lib/response-types/code-response-type.ts index 96215ae22..5e93c5aee 100755 --- a/lib/response-types/code-response-type.ts +++ b/lib/response-types/code-response-type.ts @@ -146,7 +146,7 @@ export class CodeResponseType { * Set redirect uri parameter. */ - setRedirectUriParam = (redirectUri: any, key: string, value: string) => { + setRedirectUriParam(redirectUri: any, key: string, value: string) { if (!redirectUri) { throw new InvalidArgumentError('Missing parameter: `redirectUri`'); } @@ -159,5 +159,5 @@ export class CodeResponseType { redirectUri.query[key] = value; return redirectUri; - }; + } } diff --git a/lib/response-types/token-response-type.ts b/lib/response-types/token-response-type.ts index 22c2133ae..cbf1d0f73 100755 --- a/lib/response-types/token-response-type.ts +++ b/lib/response-types/token-response-type.ts @@ -68,19 +68,19 @@ export class TokenResponseType { * Build redirect uri. */ - buildRedirectUri = (redirectUri: any) => { + buildRedirectUri(redirectUri: any) { return this.setRedirectUriParam( redirectUri, 'access_token', this.accessToken, ); - }; + } /** * Set redirect uri parameter. */ - setRedirectUriParam = (redirectUri: any, key: string, value: any) => { + setRedirectUriParam(redirectUri: any, key: string, value: any) { if (!redirectUri) { throw new InvalidArgumentError('Missing parameter: `redirectUri`'); } @@ -95,5 +95,5 @@ export class TokenResponseType { }${key}=${encodeURIComponent(value)}`; return redirectUri; - }; + } } diff --git a/lib/token-types/index.ts b/lib/token-types/index.ts new file mode 100644 index 000000000..f385166fe --- /dev/null +++ b/lib/token-types/index.ts @@ -0,0 +1,2 @@ +export { MacTokenType } from './mac-token-type'; +export { BearerTokenType } from './bearer-token-type'; diff --git a/tslint.json b/tslint.json index db33c429d..76a96ef8f 100755 --- a/tslint.json +++ b/tslint.json @@ -1,27 +1,28 @@ -{ - // "defaultSeverity": "error", - "extends": ["tslint:recommended", "tslint:all", "tslint:latest"], - "jsRules": { - "no-unused-expression": true - }, - "rules": { - "arrow-parens": false, - "completed-docs": false, - "file-name-casing": [true, "kebab-case"], - "no-object-literal-type-assertion": [false], - "interface-name": [false], - "max-line-length": [true, 120], - "member-access": [false], - "member-ordering": [false], - "no-default-export": [false], - "no-magic-numbers": [false], - "newline-per-chained-call": false, - "no-unsafe-any": false, - "object-literal-sort-keys": false, - "ordered-imports": [false], - "quotemark": [true, "single"], - "semicolon": [true, "always", "ignore-bound-class-methods"], - "strict-boolean-expressions": [false] - }, - "rulesDirectory": [] -} +{ + // "defaultSeverity": "error", + "extends": ["tslint:recommended", "tslint:all", "tslint:latest"], + "jsRules": { + "no-unused-expression": true + }, + "rules": { + "arrow-parens": false, + "completed-docs": false, + "file-name-casing": [true, "kebab-case"], + "no-object-literal-type-assertion": [false], + "interface-name": [false], + "max-line-length": [true, 120], + "member-access": [false], + "member-ordering": [false], + "no-default-export": [false], + "prefer-function-over-method": [false], + "no-magic-numbers": [false], + "newline-per-chained-call": false, + "no-unsafe-any": false, + "object-literal-sort-keys": false, + "ordered-imports": [false], + "quotemark": [true, "single"], + "semicolon": [true, "always", "ignore-bound-class-methods"], + "strict-boolean-expressions": [false] + }, + "rulesDirectory": [] +} From 8b7d9da16e7c48b448c62acc2e7f714861ead50e Mon Sep 17 00:00:00 2001 From: Ankit Date: Sat, 27 Apr 2019 22:50:24 +0530 Subject: [PATCH 09/30] rewriting-test-files --- package-lock.json | 161 +-- package.json | 12 +- .../authorization-code-grant-type.spec.ts | 32 +- .../client-credentials-grant-type.spec.ts | 4 +- .../grant-types/implicit-grant-type.spec.ts | 269 ++++ .../grant-types/password-grant-type.spec.ts | 4 +- .../refresh-token-grant-type.spec.ts | 44 +- .../handlers/authenticate-handler.spec.ts | 40 +- .../handlers/authorize-handler.spec.ts | 66 +- .../handlers/revoke-handler.spec.ts | 1112 +++++++++++++++++ .../response-types/code-response-type.spec.ts | 295 ++++- .../token-response-type.spec.ts | 96 ++ .../grant-types/implict-grant-type.spec.ts | 47 + .../handlers/authenticate-handler.spec.ts | 2 +- test/unit/handlers/authorize-handler.spec.ts | 4 +- test/unit/handlers/revoke-handlers.ts | 125 ++ test/unit/server.spec.ts | 8 +- 17 files changed, 2153 insertions(+), 168 deletions(-) create mode 100644 test/integration/grant-types/implicit-grant-type.spec.ts create mode 100644 test/integration/handlers/revoke-handler.spec.ts create mode 100644 test/integration/response-types/token-response-type.spec.ts create mode 100644 test/unit/grant-types/implict-grant-type.spec.ts create mode 100644 test/unit/handlers/revoke-handlers.ts diff --git a/package-lock.json b/package-lock.json index 0b9b9543b..8fb430565 100755 --- a/package-lock.json +++ b/package-lock.json @@ -82,9 +82,9 @@ "dev": true }, "@types/node": { - "version": "11.13.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.4.tgz", - "integrity": "sha512-+rabAZZ3Yn7tF/XPGHupKIL5EcAbrLxnTr/hgQICxbeuAfWtT0UZSfULE+ndusckBItcv4o6ZeOJplQikVcLvQ==", + "version": "11.13.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.8.tgz", + "integrity": "sha512-szA3x/3miL90ZJxUCzx9haNbK5/zmPieGraZEe4WI+3srN0eGLiT22NXeMHmyhNEopn+IrxqMc7wdVwvPl8meg==", "dev": true }, "@types/sinon": { @@ -115,9 +115,9 @@ "dev": true }, "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, "ansi-styles": { @@ -256,23 +256,6 @@ "string-width": "^2.1.1", "strip-ansi": "^4.0.0", "wrap-ansi": "^2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } } }, "code-point-at": { @@ -717,9 +700,9 @@ } }, "lolex": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-3.1.0.tgz", - "integrity": "sha512-zFo5MgCJ0rZ7gQg69S4pqBsLURbFw11X68C18OcJjJQbqaXm2NoTrGl1IMM3TIz0/BnN1tIs2tzmmqvCsOMMjw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.0.1.tgz", + "integrity": "sha512-UHuOBZ5jjsKuzbB/gRNNW8Vg8f00Emgskdq2kvZxgBJCS0aqquAuXai/SkWORlKeZEiNQWZjFZOqIUcH9LqKCw==", "dev": true }, "make-error": { @@ -760,16 +743,16 @@ "dev": true }, "mime-db": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", - "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==" + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" }, "mime-types": { - "version": "2.1.22", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", - "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", "requires": { - "mime-db": "~1.38.0" + "mime-db": "1.40.0" } }, "mimic-fn": { @@ -803,9 +786,9 @@ } }, "mocha": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.1.3.tgz", - "integrity": "sha512-QdE/w//EPHrqgT5PNRUjRVHy6IJAzAf1R8n2O8W8K2RZ+NbPfOD5cBDp+PGa2Gptep37C/TdBiaNwakppEzEbg==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.1.4.tgz", + "integrity": "sha512-PN8CIy4RXsIoxoFJzS4QNnCH4psUCPWc4/rPrst/ecSJJbLBkubMiyGCP2Kj/9YnWbotFqAoeXyXMucj7gwCFg==", "dev": true, "requires": { "ansi-colors": "3.2.3", @@ -817,7 +800,7 @@ "glob": "7.1.3", "growl": "1.10.5", "he": "1.2.0", - "js-yaml": "3.13.0", + "js-yaml": "3.13.1", "log-symbols": "2.2.0", "minimatch": "3.0.4", "mkdirp": "0.5.1", @@ -831,33 +814,6 @@ "yargs": "13.2.2", "yargs-parser": "13.0.0", "yargs-unparser": "1.5.0" - }, - "dependencies": { - "js-yaml": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.0.tgz", - "integrity": "sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - }, - "supports-color": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", - "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } } }, "ms": { @@ -1287,16 +1243,16 @@ "dev": true }, "sinon": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.3.1.tgz", - "integrity": "sha512-eQKMaeWovtOtYe2xThEvaHmmxf870Di+bim10c3ZPrL5bZhLGtu8cz+rOBTFz0CwBV4Q/7dYwZiqZbGVLZ+vjQ==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.3.2.tgz", + "integrity": "sha512-thErC1z64BeyGiPvF8aoSg0LEnptSaWE7YhdWWbWXgelOyThent7uKOnnEh9zBxDbKixtr5dEko+ws1sZMuFMA==", "dev": true, "requires": { "@sinonjs/commons": "^1.4.0", "@sinonjs/formatio": "^3.2.1", "@sinonjs/samsam": "^3.3.1", "diff": "^3.5.0", - "lolex": "^3.1.0", + "lolex": "^4.0.1", "nise": "^1.4.10", "supports-color": "^5.5.0" }, @@ -1379,23 +1335,6 @@ "requires": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } } }, "string.prototype.padend": { @@ -1410,12 +1349,12 @@ } }, "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "^3.0.0" } }, "strip-bom": { @@ -1430,6 +1369,21 @@ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, "ts-node": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.1.0.tgz", @@ -1485,18 +1439,18 @@ "dev": true }, "type-is": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", - "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "requires": { "media-typer": "0.3.0", - "mime-types": "~2.1.18" + "mime-types": "~2.1.24" } }, "typescript": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.3.tgz", - "integrity": "sha512-FFgHdPt4T/duxx6Ndf7hwgMZZjZpB+U0nMNGVCYPq0rEzWKjEDobm4J6yb3CS7naZ0yURFqdw9Gwc7UOh/P9oQ==", + "version": "3.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.5.tgz", + "integrity": "sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw==", "dev": true }, "validate-npm-package-license": { @@ -1543,6 +1497,12 @@ "strip-ansi": "^3.0.1" }, "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, "is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", @@ -1562,6 +1522,15 @@ "is-fullwidth-code-point": "^1.0.0", "strip-ansi": "^3.0.0" } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } } } }, diff --git a/package.json b/package.json index 63558decd..2f22624b3 100755 --- a/package.json +++ b/package.json @@ -39,25 +39,25 @@ "basic-auth": "^2.0.1", "lodash": "^4.17.11", "statuses": "^1.5.0", - "type-is": "^1.6.16", - "tslib": "^1.9.3" + "tslib": "^1.9.3", + "type-is": "^1.6.18" }, "devDependencies": { "@types/basic-auth": "^1.1.2", "@types/lodash": "^4.14.123", "@types/mocha": "^5.2.6", - "@types/node": "^11.13.4", + "@types/node": "^11.13.8", "@types/sinon": "^7.0.11", "@types/statuses": "^1.5.0", "@types/type-is": "^1.6.2", - "mocha": "^6.1.3", + "mocha": "^6.1.4", "npm-run-all": "^4.1.5", "should": "^13.2.3", "shx": "^0.3.2", - "sinon": "^7.3.1", + "sinon": "^7.3.2", "ts-node": "^8.1.0", "tslint": "^5.16.0", - "typescript": "^3.4.3" + "typescript": "^3.4.5" }, "license": "MIT", "engines": { diff --git a/test/integration/grant-types/authorization-code-grant-type.spec.ts b/test/integration/grant-types/authorization-code-grant-type.spec.ts index a7acff399..0cc0b2e3c 100755 --- a/test/integration/grant-types/authorization-code-grant-type.spec.ts +++ b/test/integration/grant-types/authorization-code-grant-type.spec.ts @@ -379,7 +379,9 @@ describe('AuthorizationCodeGrantType integration', () => { return grantType .getAuthorizationCode(request, client) - .then(() => should.fail('should.fail', '')) + .then(() => { + should.fail('should.fail', ''); + }) .catch(e => { e.should.be.an.instanceOf(InvalidGrantError); e.message.should.equal( @@ -410,7 +412,9 @@ describe('AuthorizationCodeGrantType integration', () => { return grantType .getAuthorizationCode(request, client) - .then(() => should.fail('should.fail', '')) + .then(() => { + should.fail('should.fail', ''); + }) .catch(e => { e.should.be.an.instanceOf(ServerError); e.message.should.equal( @@ -441,7 +445,9 @@ describe('AuthorizationCodeGrantType integration', () => { return grantType .getAuthorizationCode(request, client) - .then(() => should.fail('should.fail', '')) + .then(() => { + should.fail('should.fail', ''); + }) .catch(e => { e.should.be.an.instanceOf(ServerError); e.message.should.equal( @@ -476,7 +482,9 @@ describe('AuthorizationCodeGrantType integration', () => { return grantType .getAuthorizationCode(request, client) - .then(() => should.fail('should.fail', '')) + .then(() => { + should.fail('should.fail', ''); + }) .catch(e => { e.should.be.an.instanceOf(ServerError); e.message.should.equal( @@ -512,7 +520,9 @@ describe('AuthorizationCodeGrantType integration', () => { return grantType .getAuthorizationCode(request, client) - .then(() => should.fail('should.fail', '')) + .then(() => { + should.fail('should.fail', ''); + }) .catch(e => { e.should.be.an.instanceOf(InvalidGrantError); e.message.should.equal( @@ -549,7 +559,9 @@ describe('AuthorizationCodeGrantType integration', () => { return grantType .getAuthorizationCode(request, client) - .then(() => should.fail('should.fail', '')) + .then(() => { + should.fail('should.fail', ''); + }) .catch(e => { e.should.be.an.instanceOf(InvalidGrantError); e.message.should.equal( @@ -587,7 +599,9 @@ describe('AuthorizationCodeGrantType integration', () => { return grantType .getAuthorizationCode(request, client) - .then(() => should.fail('should.fail', '')) + .then(() => { + should.fail('should.fail', ''); + }) .catch(e => { e.should.be.an.instanceOf(InvalidGrantError); e.message.should.equal( @@ -627,7 +641,9 @@ describe('AuthorizationCodeGrantType integration', () => { .then(data => { data.should.equal(authorizationCode); }) - .catch(() => should.fail('should.fail', '')); + .catch(() => { + should.fail('should.fail', ''); + }); }); it('should support promises', () => { diff --git a/test/integration/grant-types/client-credentials-grant-type.spec.ts b/test/integration/grant-types/client-credentials-grant-type.spec.ts index 99d1dd5ac..5dd8ab11b 100755 --- a/test/integration/grant-types/client-credentials-grant-type.spec.ts +++ b/test/integration/grant-types/client-credentials-grant-type.spec.ts @@ -131,7 +131,9 @@ describe('ClientCredentialsGrantType integration', () => { .then(data => { data.should.equal(token); }) - .catch(() => should.fail('should.fail', '')); + .catch(() => { + should.fail('should.fail', ''); + }); }); it('should support promises', () => { diff --git a/test/integration/grant-types/implicit-grant-type.spec.ts b/test/integration/grant-types/implicit-grant-type.spec.ts new file mode 100644 index 000000000..d439b2ccf --- /dev/null +++ b/test/integration/grant-types/implicit-grant-type.spec.ts @@ -0,0 +1,269 @@ +import { InvalidArgumentError, Request } from 'index'; +import * as should from 'should'; +import { ImplicitGrantType } from '../../../lib/grant-types/implicit-grant-type'; + +/** + * Test `ImplicitGrantType` integration. + */ + +describe('ImplicitGrantType integration', () => { + describe('constructor()', () => { + it('should throw an error if `model` is missing', () => { + try { + new ImplicitGrantType(); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `model`'); + } + }); + + it('should throw an error if the model does not implement `saveToken()`', () => { + try { + const model = {}; + + new ImplicitGrantType({ model }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `saveToken()`', + ); + } + }); + + it('should throw an error if the `user` parameter is missing', () => { + try { + const model = { + saveToken() {}, + }; + + new ImplicitGrantType({ model }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `user`'); + } + }); + }); + + describe('handle()', () => { + it('should throw an error if `request` is missing', () => { + const model = { + saveToken() {}, + }; + const grantType: any = new ImplicitGrantType({ + accessTokenLifetime: 123, + model, + user: {}, + }); + + try { + grantType.handle(); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `request`'); + } + }); + + it('should throw an error if `client` is missing', () => { + const model = { + saveToken() {}, + }; + const grantType: any = new ImplicitGrantType({ + accessTokenLifetime: 123, + model, + user: {}, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: {}, + query: {}, + }); + + try { + grantType.handle(request, undefined); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `client`'); + } + }); + + it('should return a token', () => { + const client = { id: 'foobar' }; + const token = { accessToken: 'foobar-token' }; + const model = { + saveToken() { + return token; + }, + validateScope() { + return 'foo'; + }, + }; + const grantType: any = new ImplicitGrantType({ + accessTokenLifetime: 123, + model, + user: {}, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: {}, + query: {}, + }); + + return grantType + .handle(request, client) + .then(data => { + data.should.equal(token); + }) + .catch(should.fail); + }); + + it('should support promises', () => { + const client = { id: 'foobar' }; + const model = { + saveToken() {}, + }; + const grantType: any = new ImplicitGrantType({ + accessTokenLifetime: 123, + model, + user: {}, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: {}, + query: {}, + }); + + grantType.handle(request, client).should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const client = { id: 'foobar' }; + const model = { + saveToken() {}, + }; + const grantType: any = new ImplicitGrantType({ + accessTokenLifetime: 123, + model, + user: {}, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: {}, + query: {}, + }); + + grantType.handle(request, client).should.be.an.instanceOf(Promise); + }); + + // it('should support callbacks', () => { + // const client = { id: 'foobar' }; + // const model = { + // saveToken(tokenToSave, client, user, callback) { + // callback(null, tokenToSave); + // }, + // }; + // const grantType:any = new ImplicitGrantType({ + // accessTokenLifetime: 123, + // model, + // user: {}, + // }); + // const request = new Request({ + // body: { code: 12345 }, + // headers: {}, + // method: {}, + // query: {}, + // }); + + // grantType.handle(request, client).should.be.an.instanceOf(Promise); + // grantType.handle(request, client).then(data => { + // data.should.have.keys('accessToken', 'accessTokenExpiresAt'); + // data.accessToken.should.be.type('string'); + // }); + // }); + }); + + describe('saveToken()', () => { + it('should save the token', () => { + const token = {}; + const model = { + saveToken() { + return token; + }, + validateScope() { + return 'foo'; + }, + }; + const grantType: any = new ImplicitGrantType({ + accessTokenLifetime: 123, + model, + user: {}, + }); + + return grantType + .saveToken(token) + .then(data => { + data.should.equal(token); + }) + .catch(should.fail); + }); + + it('should support promises', () => { + const token = {}; + const model = { + saveToken() { + return Promise.resolve(token); + }, + }; + const grantType: any = new ImplicitGrantType({ + accessTokenLifetime: 123, + model, + user: {}, + }); + + grantType.saveToken(token).should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const token = {}; + const model = { + saveToken() { + return token; + }, + }; + const grantType: any = new ImplicitGrantType({ + accessTokenLifetime: 123, + model, + user: {}, + }); + + grantType.saveToken(token).should.be.an.instanceOf(Promise); + }); + + // it('should support callbacks', () => { + // const token = {}; + // const model = { + // saveToken(tokenToSave, client, user, callback) { + // callback(null, token); + // }, + // }; + // const grantType:any = new ImplicitGrantType({ + // accessTokenLifetime: 123, + // model, + // user: {}, + // }); + + // grantType.saveToken(token).should.be.an.instanceOf(Promise); + // }); + }); +}); diff --git a/test/integration/grant-types/password-grant-type.spec.ts b/test/integration/grant-types/password-grant-type.spec.ts index 1b7de2723..9c5ea01ee 100755 --- a/test/integration/grant-types/password-grant-type.spec.ts +++ b/test/integration/grant-types/password-grant-type.spec.ts @@ -124,7 +124,9 @@ describe('PasswordGrantType integration', () => { .then(data => { data.should.equal(token); }) - .catch(() => should.fail('should.fail', '')); + .catch(() => { + should.fail('should.fail', ''); + }); }); it('should support promises', () => { diff --git a/test/integration/grant-types/refresh-token-grant-type.spec.ts b/test/integration/grant-types/refresh-token-grant-type.spec.ts index 967f08e15..f66a09aeb 100755 --- a/test/integration/grant-types/refresh-token-grant-type.spec.ts +++ b/test/integration/grant-types/refresh-token-grant-type.spec.ts @@ -156,7 +156,9 @@ describe('RefreshTokenGrantType integration', () => { .then(data => { data.should.equal(token); }) - .catch(() => should.fail('should.fail', '')); + .catch(() => { + should.fail('should.fail', ''); + }); }); it('should support promises', () => { @@ -311,7 +313,9 @@ describe('RefreshTokenGrantType integration', () => { return grantType .getRefreshToken(request, client) - .then(() => should.fail('should.fail', '')) + .then(() => { + should.fail('should.fail', ''); + }) .catch(e => { e.should.be.an.instanceOf(InvalidGrantError); e.message.should.equal('Invalid grant: refresh token is invalid'); @@ -340,7 +344,9 @@ describe('RefreshTokenGrantType integration', () => { return grantType .getRefreshToken(request, client) - .then(() => should.fail('should.fail', '')) + .then(() => { + should.fail('should.fail', ''); + }) .catch(e => { e.should.be.an.instanceOf(ServerError); e.message.should.equal( @@ -371,7 +377,9 @@ describe('RefreshTokenGrantType integration', () => { return grantType .getRefreshToken(request, client) - .then(() => should.fail('should.fail', '')) + .then(() => { + should.fail('should.fail', ''); + }) .catch(e => { e.should.be.an.instanceOf(ServerError); e.message.should.equal( @@ -402,7 +410,9 @@ describe('RefreshTokenGrantType integration', () => { return grantType .getRefreshToken(request, client) - .then(() => should.fail('should.fail', '')) + .then(() => { + should.fail('should.fail', ''); + }) .catch(e => { e.should.be.an.instanceOf(InvalidGrantError); e.message.should.equal('Invalid grant: refresh token is invalid'); @@ -461,7 +471,9 @@ describe('RefreshTokenGrantType integration', () => { return grantType .getRefreshToken(request, client) - .then(() => should.fail('should.fail', '')) + .then(() => { + should.fail('should.fail', ''); + }) .catch(e => { e.should.be.an.instanceOf(InvalidGrantError); e.message.should.equal('Invalid grant: refresh token is invalid'); @@ -496,7 +508,9 @@ describe('RefreshTokenGrantType integration', () => { return grantType .getRefreshToken(request, client) - .then(() => should.fail('should.fail', '')) + .then(() => { + should.fail('should.fail', ''); + }) .catch(e => { e.should.be.an.instanceOf(InvalidGrantError); e.message.should.equal('Invalid grant: refresh token has expired'); @@ -530,7 +544,9 @@ describe('RefreshTokenGrantType integration', () => { return grantType .getRefreshToken(request, client) - .then(() => should.fail('should.fail', '')) + .then(() => { + should.fail('should.fail', ''); + }) .catch(e => { e.should.be.an.instanceOf(ServerError); e.message.should.equal( @@ -565,7 +581,9 @@ describe('RefreshTokenGrantType integration', () => { .then(data => { data.should.equal(token); }) - .catch(() => should.fail('should.fail', '')); + .catch(() => { + should.fail('should.fail', ''); + }); }); it('should support promises', () => { @@ -661,7 +679,9 @@ describe('RefreshTokenGrantType integration', () => { grantType .revokeToken({} as any) - .then(() => should.fail('should.fail', '')) + .then(() => { + should.fail('should.fail', ''); + }) .catch(e => { e.should.be.an.instanceOf(InvalidGrantError); e.message.should.equal('Invalid grant: refresh token is invalid'); @@ -692,7 +712,9 @@ describe('RefreshTokenGrantType integration', () => { .then(data => { data.should.equal(token); }) - .catch(() => should.fail('should.fail', '')); + .catch(() => { + should.fail('should.fail', ''); + }); }); it('should support promises', () => { diff --git a/test/integration/handlers/authenticate-handler.spec.ts b/test/integration/handlers/authenticate-handler.spec.ts index 522b9b233..bc28cf18a 100755 --- a/test/integration/handlers/authenticate-handler.spec.ts +++ b/test/integration/handlers/authenticate-handler.spec.ts @@ -147,7 +147,9 @@ describe('AuthenticateHandler integration', () => { return handler .handle(request, response) - .then(() => should.fail('should.fail', '')) + .then(() => { + should.fail('should.fail', ''); + }) .catch(() => { response .get('WWW-Authenticate') @@ -172,7 +174,9 @@ describe('AuthenticateHandler integration', () => { return handler .handle(request, response) - .then(() => should.fail('should.fail', '')) + .then(() => { + should.fail('should.fail', ''); + }) .catch(e => { e.should.be.an.instanceOf(AccessDeniedError); e.message.should.equal('Cannot request this access token'); @@ -196,7 +200,9 @@ describe('AuthenticateHandler integration', () => { return handler .handle(request, response) - .then(() => should.fail('should.fail', '')) + .then(() => { + should.fail('should.fail', ''); + }) .catch(e => { e.should.be.an.instanceOf(ServerError); e.message.should.equal('Unhandled exception'); @@ -235,7 +241,9 @@ describe('AuthenticateHandler integration', () => { .then(data => { data.should.equal(accessToken); }) - .catch(() => should.fail('should.fail', '')); + .catch(() => { + should.fail('should.fail', ''); + }); }); }); @@ -431,7 +439,9 @@ describe('AuthenticateHandler integration', () => { return handler .getAccessToken('foo') - .then(() => should.fail('should.fail', '')) + .then(() => { + should.fail('should.fail', ''); + }) .catch(e => { e.should.be.an.instanceOf(InvalidTokenError); e.message.should.equal('Invalid token: access token is invalid'); @@ -448,7 +458,9 @@ describe('AuthenticateHandler integration', () => { return handler .getAccessToken('foo') - .then(() => should.fail('should.fail', '')) + .then(() => { + should.fail('should.fail', ''); + }) .catch(e => { e.should.be.an.instanceOf(ServerError); e.message.should.equal( @@ -471,7 +483,9 @@ describe('AuthenticateHandler integration', () => { .then(data => { data.should.equal(accessToken); }) - .catch(() => should.fail('should.fail', '')); + .catch(() => { + should.fail('should.fail', ''); + }); }); it('should support promises', () => { @@ -557,7 +571,9 @@ describe('AuthenticateHandler integration', () => { return handler .verifyScope('foo' as any) - .then(() => should.fail('should.fail', '')) + .then(() => { + should.fail('should.fail', ''); + }) .catch(e => { e.should.be.an.instanceOf(InsufficientScopeError); e.message.should.equal( @@ -631,7 +647,7 @@ describe('AuthenticateHandler integration', () => { }); const response = new Response({ body: {}, headers: {} }); - handler.updateResponse(response, { scope: 'foo biz' }); + handler.updateResponse(response, { scope: 'foo biz' } as any); response.headers.should.not.have.property('x-accepted-oauth-scopes'); }); @@ -649,7 +665,7 @@ describe('AuthenticateHandler integration', () => { }); const response = new Response({ body: {}, headers: {} }); - handler.updateResponse(response, { scope: 'foo biz' }); + handler.updateResponse(response, { scope: 'foo biz' } as any); response.get('X-Accepted-OAuth-Scopes').should.equal('foo bar'); }); @@ -666,7 +682,7 @@ describe('AuthenticateHandler integration', () => { }); const response = new Response({ body: {}, headers: {} }); - handler.updateResponse(response, { scope: 'foo biz' }); + handler.updateResponse(response, { scope: 'foo biz' } as any); response.headers.should.not.have.property('x-oauth-scopes'); }); @@ -684,7 +700,7 @@ describe('AuthenticateHandler integration', () => { }); const response = new Response({ body: {}, headers: {} }); - handler.updateResponse(response, { scope: 'foo biz' }); + handler.updateResponse(response, { scope: 'foo biz' } as any); response.get('X-OAuth-Scopes').should.equal('foo biz'); }); diff --git a/test/integration/handlers/authorize-handler.spec.ts b/test/integration/handlers/authorize-handler.spec.ts index d8211baed..63d5b11a5 100755 --- a/test/integration/handlers/authorize-handler.spec.ts +++ b/test/integration/handlers/authorize-handler.spec.ts @@ -206,7 +206,9 @@ describe('AuthorizeHandler integration', () => { return handler .handle(request, response) - .then(() => should.fail('should.fail', '')) + .then(() => { + should.fail('should.fail', ''); + }) .catch(e => { e.should.be.an.instanceOf(AccessDeniedError); e.message.should.equal( @@ -254,7 +256,9 @@ describe('AuthorizeHandler integration', () => { return handler .handle(request, response) - .then(() => should.fail('should.fail', '')) + .then(() => { + should.fail('should.fail', ''); + }) .catch(() => { response .get('location') @@ -360,7 +364,9 @@ describe('AuthorizeHandler integration', () => { .get('location') .should.equal('http://example.com/cb?code=12345&state=foobar'); }) - .catch(() => should.fail('should.fail', '')); + .catch(() => { + should.fail('should.fail', ''); + }); }); it('should redirect to an error response if `scope` is invalid', () => { @@ -403,7 +409,9 @@ describe('AuthorizeHandler integration', () => { return handler .handle(request, response) - .then(() => should.fail('should.fail', '')) + .then(() => { + should.fail('should.fail', ''); + }) .catch(() => { response .get('location') @@ -450,7 +458,9 @@ describe('AuthorizeHandler integration', () => { return handler .handle(request, response) - .then(() => should.fail('should.fail', '')) + .then(() => { + should.fail('should.fail', ''); + }) .catch(() => { response .get('location') @@ -499,7 +509,9 @@ describe('AuthorizeHandler integration', () => { return handler .handle(request, response) - .then(() => should.fail('should.fail', '')) + .then(() => { + should.fail('should.fail', ''); + }) .catch(() => { response.get('location').should.equal( // tslint:disable-next-line:max-line-length @@ -547,7 +559,9 @@ describe('AuthorizeHandler integration', () => { return handler .handle(request, response) - .then(() => should.fail('should.fail', '')) + .then(() => { + should.fail('should.fail', ''); + }) .catch(() => { response.get('location').should.equal( // tslint:disable-next-line:max-line-length @@ -603,7 +617,9 @@ describe('AuthorizeHandler integration', () => { client, }); }) - .catch(() => should.fail('should.fail', '')); + .catch(() => { + should.fail('should.fail', ''); + }); }); }); @@ -795,7 +811,9 @@ describe('AuthorizeHandler integration', () => { return handler .getClient(request) - .then(() => should.fail('should.fail', '')) + .then(() => { + should.fail('should.fail', ''); + }) .catch(e => { e.should.be.an.instanceOf(InvalidClientError); e.message.should.equal( @@ -825,7 +843,9 @@ describe('AuthorizeHandler integration', () => { return handler .getClient(request) - .then(() => should.fail('should.fail', '')) + .then(() => { + should.fail('should.fail', ''); + }) .catch(e => { e.should.be.an.instanceOf(InvalidClientError); e.message.should.equal('Invalid client: missing client `grants`'); @@ -853,7 +873,9 @@ describe('AuthorizeHandler integration', () => { return handler .getClient(request) - .then(() => should.fail('should.fail', '')) + .then(() => { + should.fail('should.fail', ''); + }) .catch(e => { e.should.be.an.instanceOf(UnauthorizedClientError); e.message.should.equal( @@ -883,7 +905,9 @@ describe('AuthorizeHandler integration', () => { return handler .getClient(request) - .then(() => should.fail('should.fail', '')) + .then(() => { + should.fail('should.fail', ''); + }) .catch(e => { e.should.be.an.instanceOf(InvalidClientError); e.message.should.equal( @@ -920,7 +944,9 @@ describe('AuthorizeHandler integration', () => { return handler .getClient(request) - .then(() => should.fail('should.fail', '')) + .then(() => { + should.fail('should.fail', ''); + }) .catch(e => { e.should.be.an.instanceOf(InvalidClientError); e.message.should.equal( @@ -951,7 +977,7 @@ describe('AuthorizeHandler integration', () => { query: {}, }); try { - await handler.getClient(request).should.be.an.instanceOf(Promise); + handler.getClient(request).should.be.an.instanceOf(Promise); } catch (error) { should.fail('should.fail', ''); } @@ -1037,7 +1063,9 @@ describe('AuthorizeHandler integration', () => { .then(data => { data.should.equal(client); }) - .catch(() => should.fail('should.fail', '')); + .catch(() => { + should.fail('should.fail', ''); + }); }); }); }); @@ -1238,7 +1266,9 @@ describe('AuthorizeHandler integration', () => { return handler .getUser(request, response) - .then(() => should.fail('should.fail', '')) + .then(() => { + should.fail('should.fail', ''); + }) .catch(e => { e.should.be.an.instanceOf(ServerError); e.message.should.equal( @@ -1276,7 +1306,9 @@ describe('AuthorizeHandler integration', () => { .then(data => { data.should.equal(user); }) - .catch(() => should.fail('should.fail', '')); + .catch(() => { + should.fail('should.fail', ''); + }); }); }); diff --git a/test/integration/handlers/revoke-handler.spec.ts b/test/integration/handlers/revoke-handler.spec.ts new file mode 100644 index 000000000..31cd35c73 --- /dev/null +++ b/test/integration/handlers/revoke-handler.spec.ts @@ -0,0 +1,1112 @@ +import { + AccessDeniedError, + InvalidArgumentError, + InvalidClientError, + InvalidRequestError, + InvalidTokenError, + Request, + Response, + RevokeHandler, + ServerError, +} from 'index'; +import * as should from 'should'; +import * as util from 'util'; +/** + * Test `RevokeHandler` integration. + */ + +describe('RevokeHandler integration', () => { + describe('constructor()', () => { + it('should throw an error if `options.model` is missing', () => { + try { + new RevokeHandler({}); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `model`'); + } + }); + + it('should throw an error if the model does not implement `getClient()`', () => { + try { + new RevokeHandler({ model: {} }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `getClient()`', + ); + } + }); + + it('should set the `model`', () => { + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + + handler.model.should.equal(model); + }); + }); + + describe('handle()', () => { + it('should throw an error if `request` is missing', () => { + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + + try { + handler.handle(); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: `request` must be an instance of Request', + ); + } + }); + + it('should throw an error if `response` is missing', () => { + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: {}, + }); + + try { + handler.handle(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: `response` must be an instance of Response', + ); + } + }); + + it('should throw an error if the method is not `POST`', () => { + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: {}, + headers: {}, + method: 'GET', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Invalid request: method must be POST'); + }); + }); + + it('should throw an error if the media type is not `application/x-www-form-urlencoded`', () => { + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: {}, + headers: {}, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal( + 'Invalid request: content must be application/x-www-form-urlencoded', + ); + }); + }); + + it('should throw the error if an oauth error is thrown', () => { + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { token: 'hash' }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(InvalidClientError); + e.message.should.equal( + 'Invalid client: cannot retrieve client credentials', + ); + }); + }); + + it('should throw the error if an oauth error is thrown', () => { + const model = { + getClient() { + return { grants: ['password'] }; + }, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Missing parameter: `token`'); + }); + }); + + it('should throw a server error if a non-oauth error is thrown', () => { + const model = { + getClient() { + throw new Error('Unhandled exception'); + }, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { + client_id: 12345, + client_secret: 'secret', + token: 'hash', + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(ServerError); + e.message.should.equal('Unhandled exception'); + e.inner.should.be.an.instanceOf(Error); + }); + }); + + it('should update the response if an error is thrown', () => { + const model = { + getClient() { + throw new Error('Unhandled exception'); + }, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { + client_id: 12345, + client_secret: 'secret', + grant_type: 'password', + password: 'bar', + username: 'foo', + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(should.fail) + .catch(() => { + response.body.should.eql({ + error: 'server_error', + error_description: 'Unhandled exception', + }); + response.status.should.equal(500); + }); + }); + + it('should not update the response if an invalid token error is thrown', () => { + const token = { + refreshToken: 'hash', + client: {}, + user: {}, + refreshTokenExpiresAt: new Date('2015-01-01'), + }; + const client = { grants: ['password'] }; + const model = { + getClient() { + return client; + }, + revokeToken() { + return token; + }, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { + client_id: 12345, + client_secret: 'secret', + token: 'hash', + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(InvalidTokenError); + response.body.should.eql({}); + response.status.should.equal(200); + }); + }); + + it('should return an empty object if successful', () => { + const token = { + refreshToken: 'hash', + client: {}, + user: {}, + refreshTokenExpiresAt: new Date(Date.now() * 2), + }; + const client = { grants: ['password'] }; + const model = { + getClient() { + return client; + }, + revokeToken() { + return token; + }, + getRefreshToken() { + return token; + }, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { + client_id: 12345, + client_secret: 'secret', + token: 'hash', + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(data => { + should.exist(data); + }) + .catch(should.fail); + }); + }); + + describe('getClient()', () => { + it('should throw an error if `clientId` is invalid', () => { + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { client_id: 'øå€£‰', client_secret: 'foo' }, + headers: {}, + method: {}, + query: {}, + }); + + try { + handler.getClient(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Invalid parameter: `client_id`'); + } + }); + + it('should throw an error if `clientId` is invalid', () => { + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { client_id: 'foo', client_secret: 'øå€£‰' }, + headers: {}, + method: {}, + query: {}, + }); + + try { + handler.getClient(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Invalid parameter: `client_secret`'); + } + }); + + it('should throw an error if `client` is missing', () => { + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: {}, + query: {}, + }); + + return handler + .getClient(request) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(InvalidClientError); + e.message.should.equal('Invalid client: client is invalid'); + }); + }); + + it('should throw an error if `client.grants` is missing', () => { + const model = { + getClient() { + return {}; + }, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: {}, + query: {}, + }); + + return handler + .getClient(request) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(ServerError); + e.message.should.equal('Server error: missing client `grants`'); + }); + }); + + it('should throw a 401 error if the client is invalid and the request contains an authorization header', () => { + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: {}, + headers: { + authorization: util.format( + 'Basic %s', + Buffer.from('foo:bar').toString('base64'), + ), + }, + method: {}, + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .getClient(request, response) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(InvalidClientError); + e.code.should.equal(401); + e.message.should.equal('Invalid client: client is invalid'); + + response + .get('WWW-Authenticate') + .should.equal('Basic realm="Service"'); + }); + }); + + it('should return a client', () => { + const client = { id: 12345, grants: [] }; + const model = { + getClient() { + return client; + }, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: {}, + query: {}, + }); + + return handler + .getClient(request) + .then(data => { + data.should.equal(client); + }) + .catch(should.fail); + }); + + it('should support promises', () => { + const model = { + getClient() { + return Promise.resolve({ grants: [] }); + }, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: {}, + query: {}, + }); + + handler.getClient(request).should.be.an.instanceOf(Promise); + }); + + // it('should support callbacks', () => { + // const model = { + // getClient(clientId, clientSecret, callback) { + // callback(null, { grants: [] }); + // }, + // revokeToken() {}, + // getRefreshToken() {}, + // getAccessToken() {}, + // }; + // const handler:any = new RevokeHandler({ model }); + // const request = new Request({ + // body: { client_id: 12345, client_secret: 'secret' }, + // headers: {}, + // method: {}, + // query: {}, + // }); + + // handler.getClient(request).should.be.an.instanceOf(Promise); + // }); + + it('should support non-promises', () => { + const model = { + getClient() { + return { grants: [] }; + }, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: {}, + query: {}, + }); + + handler.getClient(request).should.be.an.instanceOf(Promise); + }); + }); + + describe('getClientCredentials()', () => { + it('should throw an error if `client_id` is missing', () => { + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { client_secret: 'foo' }, + headers: {}, + method: {}, + query: {}, + }); + + try { + handler.getClientCredentials(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidClientError); + e.message.should.equal( + 'Invalid client: cannot retrieve client credentials', + ); + } + }); + + it('should throw an error if `client_secret` is missing', () => { + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { client_id: 'foo' }, + headers: {}, + method: {}, + query: {}, + }); + + try { + handler.getClientCredentials(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidClientError); + e.message.should.equal( + 'Invalid client: cannot retrieve client credentials', + ); + } + }); + + describe('with `client_id` and `client_secret` in the request header as basic auth', () => { + it('should return a client', () => { + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: {}, + headers: { + authorization: util.format( + 'Basic %s', + Buffer.from('foo:bar').toString('base64'), + ), + }, + method: {}, + query: {}, + }); + const credentials = handler.getClientCredentials(request); + + credentials.should.eql({ clientId: 'foo', clientSecret: 'bar' }); + }); + }); + + describe('with `client_id` and `client_secret` in the request body', () => { + it('should return a client', () => { + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { client_id: 'foo', client_secret: 'bar' }, + headers: {}, + method: {}, + query: {}, + }); + const credentials = handler.getClientCredentials(request); + + credentials.should.eql({ clientId: 'foo', clientSecret: 'bar' }); + }); + }); + }); + + describe('handleRevokeToken()', () => { + it('should throw an error if `token` is missing', () => { + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: {}, + }); + + return handler + .handleRevokeToken(request) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Missing parameter: `token`'); + }); + }); + + it('should return a token', () => { + const client = { id: 12345, grants: ['password'] }; + const token = { + accessToken: 'hash', + client: { id: 12345 }, + accessTokenExpiresAt: new Date(Date.now() * 2), + user: {}, + }; + const model = { + getClient() {}, + revokeToken() { + return token; + }, + getRefreshToken() {}, + getAccessToken() { + return token; + }, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { token: 'hash' }, + headers: {}, + method: {}, + query: {}, + }); + + return handler + .handleRevokeToken(request, client) + .then(data => { + should.exist(data); + }) + .catch(should.fail); + }); + + it('should return a token', () => { + const client = { id: 12345, grants: ['password'] }; + const token = { + refreshToken: 'hash', + client: { id: 12345 }, + refreshTokenExpiresAt: new Date(Date.now() * 2), + user: {}, + }; + const model = { + getClient() {}, + revokeToken() { + return token; + }, + getRefreshToken() { + return token; + }, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { token: 'hash' }, + headers: {}, + method: {}, + query: {}, + }); + + return handler + .handleRevokeToken(request, client) + .then(data => { + should.exist(data); + }) + .catch(should.fail); + }); + }); + + describe('getRefreshToken()', () => { + it('should throw an error if the `refreshToken` is invalid', () => { + const client = {}; + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + + return handler + .getRefreshToken('hash', client) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(InvalidTokenError); + e.message.should.equal('Invalid token: refresh token is invalid'); + }); + }); + + it('should throw an error if the `client_id` does not match', () => { + const client = { id: 'foo' }; + const token = { + refreshToken: 'hash', + client: { id: 'baz' }, + user: {}, + refreshTokenExpiresAt: new Date(Date.now() * 2), + }; + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() { + return token; + }, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + + return handler + .getRefreshToken('hash', client) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(InvalidClientError); + e.message.should.equal('Invalid client: client is invalid'); + }); + }); + + it('should return a token', () => { + const client = { id: 'foo' }; + const token = { + refreshToken: 'hash', + client: { id: 'foo' }, + user: {}, + refreshTokenExpiresAt: new Date(Date.now() * 2), + }; + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() { + return token; + }, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + + return handler + .getRefreshToken('hash', client) + .then(Token => { + should.exist(Token); + }) + .catch(should.fail); + }); + + // it('should support callbacks', () => { + // const client = { id: 'foo' }; + // const token = { + // refreshToken: 'hash', + // client: { id: 'foo' }, + // user: {}, + // refreshTokenExpiresAt: new Date(Date.now() * 2), + // }; + // const model = { + // getClient() {}, + // revokeToken() {}, + // getRefreshToken(refreshToken, callback) { + // callback(null, token); + // }, + // getAccessToken() {}, + // }; + // const handler:any = new RevokeHandler({ model }); + + // return handler + // .getRefreshToken('hash', client) + // .then(token => { + // should.exist(token); + // }) + // .catch(should.fail); + // }); + }); + + describe('getAccessToken()', () => { + it('should throw an error if the `accessToken` is invalid', () => { + const client = {}; + const model = { + getClient() {}, + revokeToken() {}, + getAccessToken() {}, + getRefreshToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + + return handler + .getAccessToken('hash', client) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(InvalidTokenError); + e.message.should.equal('Invalid token: access token is invalid'); + }); + }); + + it('should throw an error if the `client_id` does not match', () => { + const client = { id: 'foo' }; + const token = { + accessToken: 'hash', + client: { id: 'baz' }, + user: {}, + accessTokenExpiresAt: new Date(Date.now() * 2), + }; + const model = { + getClient() {}, + revokeToken() {}, + getAccessToken() { + return token; + }, + getRefreshToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + + return handler + .getAccessToken('hash', client) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(InvalidClientError); + e.message.should.equal('Invalid client: client is invalid'); + }); + }); + + it('should return a token', () => { + const client = { id: 'foo' }; + const token = { + accessToken: 'hash', + client: { id: 'foo' }, + user: {}, + accessTokenExpiresAt: new Date(Date.now() * 2), + }; + const model = { + getClient() {}, + revokeToken() {}, + getAccessToken() { + return token; + }, + getRefreshToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + + return handler + .getAccessToken('hash', client) + .then(Token => { + should.exist(Token); + }) + .catch(should.fail); + }); + + // it('should support callbacks', () => { + // const client = { id: 'foo' }; + // const token = { + // accessToken: 'hash', + // client: { id: 'foo' }, + // user: {}, + // accessTokenExpiresAt: new Date(Date.now() * 2), + // }; + // const model = { + // getClient() {}, + // revokeToken() {}, + // getAccessToken(accessToken, callback) { + // callback(null, token); + // }, + // getRefreshToken() {}, + // }; + // const handler:any = new RevokeHandler({ model }); + + // return handler + // .getAccessToken('hash', client) + // .then(token => { + // should.exist(token); + // }) + // .catch(should.fail); + // }); + }); + + describe('revokeToken()', () => { + it('should throw an error if the `refreshToken` is invalid', () => { + const token = 'hash'; + const client = {}; + const model = { + getClient() {}, + revokeToken() { + return false; + }, + getRefreshToken() { + return { client: {}, user: {} }; + }, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + + return handler + .revokeToken(token, client) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(InvalidTokenError); + e.message.should.equal('Invalid token: token is invalid'); + }); + }); + + // it('should support callbacks', () => { + // const token = {}; + // const client = {}; + // const model = { + // getClient() {}, + // revokeToken(tokenObject, callback) { + // callback(null, null); + // }, + // getRefreshToken(refreshToken, callback) { + // callback(null, { client: {}, user: {} }); + // }, + // getAccessToken() {}, + // }; + // const handler:any = new RevokeHandler({ model }); + + // return handler + // .revokeToken(token, client) + // .then(should.fail) + // .catch(e => { + // e.should.be.an.instanceOf(InvalidTokenError); + // e.message.should.equal('Invalid token: token is invalid'); + // }); + // }); + }); + + describe('getTokenFromRequest()', () => { + it('should throw an error if `accessToken` is missing', () => { + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: {}, + headers: {}, + method: {}, + query: {}, + }); + + try { + handler.getTokenFromRequest(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Missing parameter: `token`'); + } + }); + }); + + describe('updateErrorResponse()', () => { + it('should set the `body`', () => { + const error = new AccessDeniedError('Cannot request a revoke'); + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const response = new Response({ body: {}, headers: {} }); + + handler.updateErrorResponse(response, error); + + response.body.error.should.equal('access_denied'); + response.body.error_description.should.equal('Cannot request a revoke'); + }); + + it('should set the `status`', () => { + const error = new AccessDeniedError('Cannot request a revoke'); + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const response = new Response({ body: {}, headers: {} }); + + handler.updateErrorResponse(response, error); + + response.status.should.equal(400); + }); + }); +}); diff --git a/test/integration/response-types/code-response-type.spec.ts b/test/integration/response-types/code-response-type.spec.ts index 9788ace3c..029f40cde 100755 --- a/test/integration/response-types/code-response-type.spec.ts +++ b/test/integration/response-types/code-response-type.spec.ts @@ -1,4 +1,5 @@ import * as should from 'should'; +import * as sinon from 'sinon'; import * as url from 'url'; import { InvalidArgumentError } from '../../../lib/errors/invalid-argument-error'; import { CodeResponseType } from '../../../lib/response-types/code-response-type'; @@ -9,27 +10,66 @@ import { CodeResponseType } from '../../../lib/response-types/code-response-type describe('CodeResponseType integration', () => { describe('constructor()', () => { - it('should throw an error if `code` is missing', () => { + it('should throw an error if `options.authorizationCodeLifetime` is missing', () => { try { - new CodeResponseType(undefined); + new CodeResponseType(); should.fail('should.fail', ''); } catch (e) { e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `code`'); + e.message.should.equal( + 'Missing parameter: `authorizationCodeLifetime`', + ); } }); it('should set the `code`', () => { - const responseType = new CodeResponseType('foo' as any); + const model = { + saveAuthorizationCode: () => {}, + }; + const responseType = new CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); - responseType.code.should.equal('foo'); + responseType.authorizationCodeLifetime.should.equal(120); }); }); + it('should throw an error if the model does not implement `saveAuthorizationCode()`', () => { + try { + new CodeResponseType({ authorizationCodeLifetime: 120, model: {} }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `saveAuthorizationCode()`', + ); + } + }); + + it('should set the `authorizationCodeLifetime`', () => { + const model = { + saveAuthorizationCode: () => {}, + }; + const handler = new CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + + handler.authorizationCodeLifetime.should.equal(120); + }); + describe('buildRedirectUri()', () => { it('should throw an error if the `redirectUri` is missing', () => { - const responseType = new CodeResponseType('foo' as any); + const model = { + saveAuthorizationCode: () => {}, + }; + const responseType = new CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); try { responseType.buildRedirectUri(undefined); @@ -42,18 +82,32 @@ describe('CodeResponseType integration', () => { }); it('should return the new redirect uri and set the `code` and `state` in the query', () => { - const responseType = new CodeResponseType('foo' as any); + const model = { + saveAuthorizationCode: () => {}, + }; + const responseType = new CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + responseType.code = 'foo'; const redirectUri = responseType.buildRedirectUri( - 'http://example.com/cb', + url.parse('http://example.com/cb'), ); url.format(redirectUri).should.equal('http://example.com/cb?code=foo'); }); it('should return the new redirect uri and append the `code` and `state` in the query', () => { - const responseType = new CodeResponseType('foo' as any); + const model = { + saveAuthorizationCode: () => {}, + }; + const responseType = new CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + responseType.code = 'foo'; const redirectUri = responseType.buildRedirectUri( - 'http://example.com/cb?foo=bar', + url.parse('http://example.com/cb?foo=bar', true), ); url @@ -61,4 +115,225 @@ describe('CodeResponseType integration', () => { .should.equal('http://example.com/cb?foo=bar&code=foo'); }); }); + + it('should set the `model`', () => { + const model = { + saveAuthorizationCode: () => {}, + }; + const handler = new CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + + handler.model.should.equal(model); + }); + + describe('generateAuthorizationCode()', () => { + it('should return an auth code', () => { + const model = { + getAccessToken: () => {}, + getClient: () => {}, + saveAuthorizationCode: () => {}, + }; + const handler = new CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + + return handler + .generateAuthorizationCode(undefined, undefined, undefined) + .then((data: any) => { + data.should.be.a.sha1(); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + + it('should support promises', () => { + const model = { + generateAuthorizationCode: () => { + return Promise.resolve({}); + }, + getAccessToken: () => {}, + getClient: () => {}, + saveAuthorizationCode: () => {}, + }; + const handler = new CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + + handler + .generateAuthorizationCode(undefined, undefined, undefined) + .should.be.an.instanceOf(Promise); + }); + + // it('should support non-promises', () => { + // const model = { + // generateAuthorizationCode: () => { + // return {}; + // }, + // getAccessToken: () => {}, + // getClient: () => {}, + // saveAuthorizationCode: () => {}, + // }; + // const handler = new CodeResponseType({ + // authorizationCodeLifetime: 120, + // model, + // }); + + // handler + // .generateAuthorizationCode(undefined, undefined, undefined) + // .should.be.an.instanceOf(Promise); + // }); + }); + + describe('getAuthorizationCodeExpiresAt()', () => { + it('should return a date', () => { + const model = { + getAccessToken: () => {}, + getClient: () => {}, + saveAuthorizationCode: () => {}, + }; + const handler: any = new CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + + handler.getAuthorizationCodeExpiresAt({}).should.be.an.instanceOf(Date); + }); + }); + + describe('saveAuthorizationCode()', () => { + it('should return an auth code', () => { + const authorizationCode = {}; + const model = { + getAccessToken: () => {}, + getClient: () => {}, + saveAuthorizationCode: () => { + return authorizationCode; + }, + }; + const handler: any = new CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + + return handler + .saveAuthorizationCode('foo', 'bar', 'biz', 'baz') + .then(data => { + data.should.equal(authorizationCode); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + + it('should support promises when calling `model.saveAuthorizationCode()`', () => { + const model = { + getAccessToken: () => {}, + getClient: () => {}, + saveAuthorizationCode: () => { + return Promise.resolve({}); + }, + }; + const handler: any = new CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + + handler + .saveAuthorizationCode('foo', 'bar', 'biz', 'baz', undefined, undefined) + .should.be.an.instanceOf(Promise); + }); + + // it('should support non-promises when calling `model.saveAuthorizationCode()`', () => { + // const model = { + // getAccessToken: () => {}, + // getClient: () => {}, + // saveAuthorizationCode: () => { + // return {}; + // }, + // }; + // const handler = new CodeResponseType({ + // authorizationCodeLifetime: 120, + // model, + // }); + + // handler + // .saveAuthorizationCode( + // 'foo', + // 'bar' as any, + // 'biz', + // 'baz' as any, + // undefined, + // undefined, + // ) + // .should.be.an.instanceOf(Promise); + // }); + }); + + describe('saveAuthorizationCode()', () => { + it('should call `model.saveAuthorizationCode()`', () => { + const model = { + getAccessToken: () => {}, + getClient: () => {}, + saveAuthorizationCode: sinon.stub().returns({}), + }; + const handler = new CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + + return handler + .saveAuthorizationCode( + 'foo', + 'bar' as any, + 'qux', + 'biz' as any, + 'baz', + 'boz' as any, + ) + .then(() => { + model.saveAuthorizationCode.callCount.should.equal(1); + model.saveAuthorizationCode.firstCall.args.should.have.length(3); + model.saveAuthorizationCode.firstCall.args[0].should.eql({ + authorizationCode: 'foo', + expiresAt: 'bar', + redirectUri: 'baz', + scope: 'qux', + }); + model.saveAuthorizationCode.firstCall.args[1].should.equal('biz'); + model.saveAuthorizationCode.firstCall.args[2].should.equal('boz'); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + + describe('generateAuthorizationCode()', () => { + it('should call `model.generateAuthorizationCode()`', () => { + const model = { + generateAuthorizationCode: sinon.stub().returns({}), + getAccessToken: () => {}, + getClient: () => {}, + saveAuthorizationCode: () => {}, + }; + const handler = new CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + + return handler + .generateAuthorizationCode(undefined, undefined, undefined) + .then(() => { + model.generateAuthorizationCode.callCount.should.equal(1); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); }); diff --git a/test/integration/response-types/token-response-type.spec.ts b/test/integration/response-types/token-response-type.spec.ts new file mode 100644 index 000000000..a959603d5 --- /dev/null +++ b/test/integration/response-types/token-response-type.spec.ts @@ -0,0 +1,96 @@ +import * as should from 'should'; +import * as url from 'url'; +import { InvalidArgumentError } from '../../../lib/errors/invalid-argument-error'; +import { TokenResponseType } from '../../../lib/response-types/token-response-type'; + +/** + * Test `TokenResponseType` integration. + */ + +describe('TokenResponseType integration', () => { + describe('constructor()', () => { + it('should throw an error if `options.accessTokenLifetime` is missing', () => { + try { + new TokenResponseType(); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `accessTokenLifetime`'); + } + }); + + it('should set `accessTokenLifetime`', () => { + const responseType = new TokenResponseType({ + accessTokenLifetime: 120, + model: {}, + }); + + responseType.accessTokenLifetime.should.equal(120); + }); + + it('should set the `model`', () => { + const model = { + foobar() {}, + }; + const handler = new TokenResponseType({ + accessTokenLifetime: 120, + model, + }); + + handler.model.should.equal(model); + }); + }); + + describe('buildRedirectUri()', () => { + it('should throw an error if the `redirectUri` is missing', () => { + const responseType = new TokenResponseType({ + accessTokenLifetime: 120, + model: {}, + }); + + try { + responseType.buildRedirectUri(undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `redirectUri`'); + } + }); + + it('should return the new redirect uri and set `access_token` and `state` in the query', () => { + const responseType = new TokenResponseType({ + accessTokenLifetime: 120, + model: {}, + }); + + responseType.accessToken = 'foobar-token'; + const redirectUri = responseType.buildRedirectUri( + url.parse('http://example.com/cb'), + ); + + url + .format(redirectUri) + .should.equal('http://example.com/cb#access_token=foobar-token'); + }); + + it('should return the new redirect uri and append `access_token` and `state` in the query', () => { + const responseType = new TokenResponseType({ + accessTokenLifetime: 120, + model: {}, + }); + + responseType.accessToken = 'foobar-token'; + const redirectUri = responseType.buildRedirectUri( + url.parse('http://example.com/cb?foo=bar', true), + ); + + url + .format(redirectUri) + .should.equal( + 'http://example.com/cb?foo=bar#access_token=foobar-token', + ); + }); + }); +}); diff --git a/test/unit/grant-types/implict-grant-type.spec.ts b/test/unit/grant-types/implict-grant-type.spec.ts new file mode 100644 index 000000000..f6a34a6eb --- /dev/null +++ b/test/unit/grant-types/implict-grant-type.spec.ts @@ -0,0 +1,47 @@ +import * as should from 'should'; +import * as sinon from 'sinon'; +import { ImplicitGrantType } from '../../../lib/grant-types/implicit-grant-type'; +/** + * Test `ImplicitGrantType`. + */ + +describe('ImplicitGrantType', () => { + describe('saveToken()', () => { + it('should call `model.saveToken()`', () => { + const client = {}; + const user = {}; + const model = { + saveToken: sinon.stub().returns(true), + }; + const handler: any = new ImplicitGrantType({ + accessTokenLifetime: 120, + model, + user, + }); + + sinon.stub(handler, 'validateScope').returns('foobar-scope'); + sinon + .stub(handler, 'generateAccessToken') + .returns(Promise.resolve('foobar-token')); + sinon + .stub(handler, 'getAccessTokenExpiresAt') + .returns(Promise.resolve('foo-1234')); + + return handler + .saveToken(user, client, 'foobar') + .then(() => { + model.saveToken.callCount.should.equal(1); + model.saveToken.firstCall.args.should.have.length(3); + model.saveToken.firstCall.args[0].should.eql({ + accessToken: 'foobar-token', + accessTokenExpiresAt: 'foo-1234', + scope: 'foobar-scope', + }); + model.saveToken.firstCall.args[1].should.equal(client); + model.saveToken.firstCall.args[2].should.equal(user); + model.saveToken.firstCall.thisValue.should.equal(model); + }) + .catch(should.fail); + }); + }); +}); diff --git a/test/unit/handlers/authenticate-handler.spec.ts b/test/unit/handlers/authenticate-handler.spec.ts index 97cef5560..a1ba57e99 100755 --- a/test/unit/handlers/authenticate-handler.spec.ts +++ b/test/unit/handlers/authenticate-handler.spec.ts @@ -111,7 +111,7 @@ describe('AuthenticateHandler', () => { let failed = false; try { - await handler.validateAccessToken({ + handler.validateAccessToken({ user: {}, } as any); } catch (err) { diff --git a/test/unit/handlers/authorize-handler.spec.ts b/test/unit/handlers/authorize-handler.spec.ts index 20f5951c8..34254fdfc 100755 --- a/test/unit/handlers/authorize-handler.spec.ts +++ b/test/unit/handlers/authorize-handler.spec.ts @@ -95,7 +95,9 @@ describe('AuthorizeHandler', () => { authenticateHandler.handle.firstCall.args[0].should.equal(request); authenticateHandler.handle.firstCall.args[1].should.equal(response); }) - .catch(() => should.fail('should.fail', '')); + .catch(() => { + should.fail('should.fail', ''); + }); }); }); diff --git a/test/unit/handlers/revoke-handlers.ts b/test/unit/handlers/revoke-handlers.ts new file mode 100644 index 000000000..72fdbec3a --- /dev/null +++ b/test/unit/handlers/revoke-handlers.ts @@ -0,0 +1,125 @@ +import { Request, RevokeHandler } from 'index'; +import * as should from 'should'; +import * as sinon from 'sinon'; + +/** + * Test `RevokeHandler`. + */ + +describe('RevokeHandler', () => { + describe('handleRevokeToken()', () => { + it('should call `model.getAccessToken()` and `model.getRefreshToken()`', () => { + const model = { + getClient() {}, + revokeToken: sinon.stub().returns(true), + getRefreshToken: sinon.stub().returns({ + refreshToken: 'hash', + client: {}, + refreshTokenExpiresAt: new Date(Date.now() * 2), + user: {}, + }), + getAccessToken: sinon.stub().returns(false), + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { token: 'foo' }, + headers: {}, + method: {}, + query: {}, + }); + const client = {}; + + return handler + .handleRevokeToken(request, client) + .then(() => { + model.getAccessToken.callCount.should.equal(1); + model.getAccessToken.firstCall.args[0].should.equal('foo'); + model.getRefreshToken.callCount.should.equal(1); + model.getRefreshToken.firstCall.args[0].should.equal('foo'); + }) + .catch(should.fail); + }); + }); + + describe('getClient()', () => { + it('should call `model.getClient()`', () => { + const model = { + getClient: sinon.stub().returns({ grants: ['password'] }), + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: {}, + query: {}, + }); + + return handler + .getClient(request) + .then(() => { + model.getClient.callCount.should.equal(1); + model.getClient.firstCall.args.should.have.length(2); + model.getClient.firstCall.args[0].should.equal(12345); + model.getClient.firstCall.args[1].should.equal('secret'); + }) + .catch(should.fail); + }); + }); + + describe('getRefreshToken()', () => { + it('should call `model.getRefreshToken()`', () => { + const model = { + getClient() {}, + revokeToken() {}, + getAccessToken() {}, + getRefreshToken: sinon.stub().returns({ + refreshToken: 'hash', + client: {}, + refreshTokenExpiresAt: new Date(Date.now() * 2), + user: {}, + }), + }; + const handler: any = new RevokeHandler({ model }); + const token = 'hash'; + const client = {}; + + return handler + .getRefreshToken(token, client) + .then(() => { + model.getRefreshToken.callCount.should.equal(1); + model.getRefreshToken.firstCall.args.should.have.length(1); + model.getRefreshToken.firstCall.args[0].should.equal(token); + }) + .catch(should.fail); + }); + }); + + describe('revokeToken()', () => { + it('should call `model.revokeToken()`', () => { + const model = { + getClient() {}, + revokeToken: sinon.stub().returns(true), + getRefreshToken: sinon.stub().returns({ + refreshToken: 'hash', + client: {}, + refreshTokenExpiresAt: new Date(Date.now() * 2), + user: {}, + }), + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const token = 'hash'; + + return handler + .revokeToken(token) + .then(() => { + model.revokeToken.callCount.should.equal(1); + model.revokeToken.firstCall.args.should.have.length(1); + }) + .catch(should.fail); + }); + }); +}); diff --git a/test/unit/server.spec.ts b/test/unit/server.spec.ts index 71af87ccc..58d88de62 100755 --- a/test/unit/server.spec.ts +++ b/test/unit/server.spec.ts @@ -13,7 +13,7 @@ const Token: any = TokenHandler; describe('Server', () => { describe('authenticate()', () => { - it('should call `handle`', () => { + it('should call `handle`', async () => { const model = { getAccessToken() {}, }; @@ -21,14 +21,14 @@ describe('Server', () => { sinon.stub(Authenticate.prototype, 'handle').returns(Promise.resolve()); - server.authenticate('foo' as any); + await server.authenticate('foo' as any); Authenticate.prototype.handle.callCount.should.equal(1); Authenticate.prototype.handle.firstCall.args[0].should.equal('foo'); Authenticate.prototype.handle.restore(); }); - it('should map string passed as `options` to `options.scope`', () => { + it('should map string passed as `options` to `options.scope`', async () => { const model = { getAccessToken() {}, verifyScope() {}, @@ -37,7 +37,7 @@ describe('Server', () => { sinon.stub(Authenticate.prototype, 'handle').returns(Promise.resolve()); - server.authenticate('foo' as any, 'bar' as any, 'test'); + await server.authenticate('foo' as any, 'bar' as any, 'test'); Authenticate.prototype.handle.callCount.should.equal(1); Authenticate.prototype.handle.firstCall.args[0].should.equal('foo'); From de35cc1f8ed308c72d966ddee9fb7cfbae2f8842 Mon Sep 17 00:00:00 2001 From: Ankit Date: Tue, 30 Apr 2019 22:12:01 +0530 Subject: [PATCH 10/30] fixed-tests --- .../authorization-code-grant-type.ts | 6 +- lib/grant-types/index.ts | 1 + lib/grant-types/refresh-token-grant-type.ts | 4 +- lib/handlers/revoke-handler.ts | 8 +- lib/handlers/token-handler.ts | 18 +-- lib/models/token-model.ts | 2 +- lib/response-types/token-response-type.ts | 2 +- lib/response.ts | 2 +- lib/token-types/index.ts | 2 +- .../grant-types/abstract-grant-type.spec.ts | 4 +- .../authorization-code-grant-type.spec.ts | 24 ++-- .../client-credentials-grant-type.spec.ts | 5 +- .../grant-types/implicit-grant-type.spec.ts | 20 ++-- .../grant-types/password-grant-type.spec.ts | 10 +- .../refresh-token-grant-type.spec.ts | 12 +- .../handlers/authenticate-handler.spec.ts | 18 +-- .../handlers/authorize-handler.spec.ts | 107 +++++++++++------- .../handlers/revoke-handler.spec.ts | 32 +++--- .../handlers/token-handler.spec.ts | 74 ++++++------ test/integration/request.spec.ts | 2 +- .../response-types/code-response-type.spec.ts | 4 +- .../token-response-type.spec.ts | 4 +- test/integration/server.spec.ts | 33 +++--- .../token-types/bearer-token-type.spec.ts | 4 +- .../grant-types/abstract-grant-type.spec.ts | 2 +- .../authorization-code-grant-type.spec.ts | 4 +- .../client-credentials-grant-type.spec.ts | 2 +- .../grant-types/implict-grant-type.spec.ts | 2 +- .../grant-types/password-grant-type.spec.ts | 2 +- .../refresh-token-grant-type.spec.ts | 10 +- .../handlers/authenticate-handler.spec.ts | 4 +- test/unit/handlers/authorize-handler.spec.ts | 2 +- test/unit/handlers/revoke-handlers.ts | 3 +- test/unit/handlers/token-handler.spec.ts | 2 +- test/unit/server.spec.ts | 8 +- tslint.json | 57 +++++----- 36 files changed, 263 insertions(+), 233 deletions(-) diff --git a/lib/grant-types/authorization-code-grant-type.ts b/lib/grant-types/authorization-code-grant-type.ts index 19f21d4ee..5f1e48b9b 100755 --- a/lib/grant-types/authorization-code-grant-type.ts +++ b/lib/grant-types/authorization-code-grant-type.ts @@ -5,7 +5,7 @@ import { InvalidRequestError, ServerError, } from '../errors'; -import { Client, Token, User } from '../interfaces'; +import { AuthorizationCode, Client, Token, User } from '../interfaces'; import { Request } from '../request'; import * as is from '../validator/is'; @@ -131,7 +131,7 @@ export class AuthorizationCodeGrantType extends AbstractGrantType { * @see https://tools.ietf.org/html/rfc6749#section-4.1.3 */ - validateRedirectUri(request: Request, code) { + validateRedirectUri(request: Request, code: AuthorizationCode) { if (!code.redirectUri) { return; } @@ -161,7 +161,7 @@ export class AuthorizationCodeGrantType extends AbstractGrantType { * @see https://tools.ietf.org/html/rfc6749#section-4.1.2 */ - async revokeAuthorizationCode(code) { + async revokeAuthorizationCode(code: AuthorizationCode) { const status = await this.model.revokeAuthorizationCode(code); if (!status) { throw new InvalidGrantError( diff --git a/lib/grant-types/index.ts b/lib/grant-types/index.ts index d766ce46a..3d0ad0d4b 100644 --- a/lib/grant-types/index.ts +++ b/lib/grant-types/index.ts @@ -1,5 +1,6 @@ export { AbstractGrantType } from './abstract-grant-type'; export { AuthorizationCodeGrantType } from './authorization-code-grant-type'; export { ClientCredentialsGrantType } from './client-credentials-grant-type'; +export { ImplicitGrantType } from './implicit-grant-type'; export { PasswordGrantType } from './password-grant-type'; export { RefreshTokenGrantType } from './refresh-token-grant-type'; diff --git a/lib/grant-types/refresh-token-grant-type.ts b/lib/grant-types/refresh-token-grant-type.ts index d2ea685a6..2137d7eb4 100755 --- a/lib/grant-types/refresh-token-grant-type.ts +++ b/lib/grant-types/refresh-token-grant-type.ts @@ -118,7 +118,7 @@ export class RefreshTokenGrantType extends AbstractGrantType { */ async revokeToken(token: RefreshToken) { - if (!this.alwaysIssueNewRefreshToken) { + if (this.alwaysIssueNewRefreshToken === false) { return token; } @@ -155,7 +155,7 @@ export class RefreshTokenGrantType extends AbstractGrantType { scope, }; - if (this.alwaysIssueNewRefreshToken) { + if (this.alwaysIssueNewRefreshToken !== false) { token.refreshToken = refreshToken; token.refreshTokenExpiresAt = refreshTokenExpiresAt; } diff --git a/lib/handlers/revoke-handler.ts b/lib/handlers/revoke-handler.ts index 6d0dcab98..c57bac321 100644 --- a/lib/handlers/revoke-handler.ts +++ b/lib/handlers/revoke-handler.ts @@ -108,14 +108,14 @@ export class RevokeHandler { */ async handleRevokeToken(request: Request, client: Client) { - const token = await this.getTokenFromRequest(request); try { - const t = await oneSuccess([ + let token = await this.getTokenFromRequest(request); + token = await oneSuccess([ this.getAccessToken(token, client), this.getRefreshToken(token, client), ]); - return this.revokeToken(t); + return this.revokeToken(token); } catch (errors) { throw errors; } @@ -269,7 +269,7 @@ export class RevokeHandler { * Get the access token from the model. */ - async getAccessToken(token, client) { + async getAccessToken(token: string, client: Client) { const accessToken = await this.model.getAccessToken(token); if (!accessToken) { throw new InvalidTokenError('Invalid token: access token is invalid'); diff --git a/lib/handlers/token-handler.ts b/lib/handlers/token-handler.ts index 71463d3e7..e5e448ed2 100755 --- a/lib/handlers/token-handler.ts +++ b/lib/handlers/token-handler.ts @@ -14,6 +14,7 @@ import { PasswordGrantType, RefreshTokenGrantType, } from '../grant-types'; +import { Model } from '../interfaces'; import { Client } from '../interfaces/client.interface'; import { TokenModel } from '../models/token-model'; import { Request } from '../request'; @@ -32,13 +33,12 @@ const grantTypes = { password: PasswordGrantType, refresh_token: RefreshTokenGrantType, }; - export class TokenHandler { accessTokenLifetime: any; - grantTypes: any; - model: any; - refreshTokenLifetime: any; - allowExtendedTokenAttributes: any; + grantTypes: { [key: string]: any }; + model: Model; + refreshTokenLifetime: number; + allowExtendedTokenAttributes: boolean; requireClientAuthentication: any; alwaysIssueNewRefreshToken: boolean; constructor(options: any = {}) { @@ -221,7 +221,7 @@ export class TokenHandler { * Handle grant type. */ - async handleGrantType(request: Request, client) { + async handleGrantType(request: Request, client: Client) { const grantType = request.body.grant_type; if (!grantType) { @@ -246,7 +246,7 @@ export class TokenHandler { const accessTokenLifetime = this.getAccessTokenLifetime(client); const refreshTokenLifetime = this.getRefreshTokenLifetime(client); - const Type = this.grantTypes[grantType]; + const GrantType = this.grantTypes[grantType]; const options = { accessTokenLifetime, @@ -255,14 +255,14 @@ export class TokenHandler { alwaysIssueNewRefreshToken: this.alwaysIssueNewRefreshToken, }; - return new Type(options).handle(request, client); + return new GrantType(options).handle(request, client); } /** * Get access token lifetime. */ - getAccessTokenLifetime(client) { + getAccessTokenLifetime(client: Client) { return client.accessTokenLifetime || this.accessTokenLifetime; } diff --git a/lib/models/token-model.ts b/lib/models/token-model.ts index 766762181..f2b560b00 100755 --- a/lib/models/token-model.ts +++ b/lib/models/token-model.ts @@ -68,7 +68,7 @@ export class TokenModel implements Token { this.customAttributes = {}; for (const key in data) { - if (hasOwnProperty(key, data) && modelAttributes.indexOf(key) < 0) { + if (hasOwnProperty(data, key) && modelAttributes.indexOf(key) < 0) { this.customAttributes[key] = data[key]; } } diff --git a/lib/response-types/token-response-type.ts b/lib/response-types/token-response-type.ts index cbf1d0f73..c2753b05b 100755 --- a/lib/response-types/token-response-type.ts +++ b/lib/response-types/token-response-type.ts @@ -1,5 +1,5 @@ import { InvalidArgumentError } from '../errors/invalid-argument-error'; -import { ImplicitGrantType } from '../grant-types/implicit-grant-type'; +import { ImplicitGrantType } from '../grant-types'; import { Client } from '../interfaces/client.interface'; import { Model } from '../interfaces/model.interface'; import { User } from '../interfaces/user.interface'; diff --git a/lib/response.ts b/lib/response.ts index b9b576b9b..2891b25f6 100755 --- a/lib/response.ts +++ b/lib/response.ts @@ -11,7 +11,7 @@ export class Response { // Store the headers in lower case. for (const field in options.headers) { - if (hasOwnProperty(options, field)) { + if (hasOwnProperty(options.headers, field)) { this.headers[field.toLowerCase()] = options.headers[field]; } } diff --git a/lib/token-types/index.ts b/lib/token-types/index.ts index f385166fe..7fa71bce4 100644 --- a/lib/token-types/index.ts +++ b/lib/token-types/index.ts @@ -1,2 +1,2 @@ -export { MacTokenType } from './mac-token-type'; export { BearerTokenType } from './bearer-token-type'; +export { MacTokenType } from './mac-token-type'; diff --git a/test/integration/grant-types/abstract-grant-type.spec.ts b/test/integration/grant-types/abstract-grant-type.spec.ts index c32478450..23a8c37cd 100755 --- a/test/integration/grant-types/abstract-grant-type.spec.ts +++ b/test/integration/grant-types/abstract-grant-type.spec.ts @@ -1,6 +1,6 @@ import * as should from 'should'; -import { InvalidArgumentError } from '../../../lib/errors/invalid-argument-error'; -import { AbstractGrantType } from '../../../lib/grant-types/abstract-grant-type'; +import { InvalidArgumentError } from '../../../lib/errors'; +import { AbstractGrantType } from '../../../lib/grant-types'; import { Request } from '../../../lib/request'; /** * Test `AbstractGrantType` integration. diff --git a/test/integration/grant-types/authorization-code-grant-type.spec.ts b/test/integration/grant-types/authorization-code-grant-type.spec.ts index 0cc0b2e3c..88c70fd10 100755 --- a/test/integration/grant-types/authorization-code-grant-type.spec.ts +++ b/test/integration/grant-types/authorization-code-grant-type.spec.ts @@ -1,9 +1,11 @@ import * as should from 'should'; -import { InvalidArgumentError } from '../../../lib/errors/invalid-argument-error'; -import { InvalidGrantError } from '../../../lib/errors/invalid-grant-error'; -import { InvalidRequestError } from '../../../lib/errors/invalid-request-error'; -import { ServerError } from '../../../lib/errors/server-error'; -import { AuthorizationCodeGrantType } from '../../../lib/grant-types/authorization-code-grant-type'; +import { + InvalidArgumentError, + InvalidGrantError, + InvalidRequestError, + ServerError, +} from '../../../lib/errors'; +import { AuthorizationCodeGrantType } from '../../../lib/grant-types'; import { Request } from '../../../lib/request'; /** @@ -742,7 +744,7 @@ describe('AuthorizationCodeGrantType integration', () => { describe('validateRedirectUri()', () => { it('should throw an error if `redirectUri` is missing', () => { - const authorizationCode = { + const authorizationCode: any = { authorizationCode: 12345, client: {}, expiresAt: new Date(new Date().getTime() / 2), @@ -780,7 +782,7 @@ describe('AuthorizationCodeGrantType integration', () => { }); it('should throw an error if `redirectUri` is invalid', () => { - const authorizationCode = { + const authorizationCode: any = { authorizationCode: 12345, client: {}, expiresAt: new Date(new Date().getTime() / 2), @@ -818,7 +820,7 @@ describe('AuthorizationCodeGrantType integration', () => { describe('revokeAuthorizationCode()', () => { it('should revoke the auth code', async () => { - const authorizationCode = { + const authorizationCode: any = { authorizationCode: 12345, client: {}, expiresAt: new Date(new Date().getTime() / 2), @@ -844,7 +846,7 @@ describe('AuthorizationCodeGrantType integration', () => { }); it('should throw an error when the auth code is invalid', () => { - const authorizationCode = { + const authorizationCode: any = { authorizationCode: 12345, client: {}, expiresAt: new Date(new Date().getTime() / 2), @@ -876,7 +878,7 @@ describe('AuthorizationCodeGrantType integration', () => { }); it('should support promises', () => { - const authorizationCode = { + const authorizationCode: any = { authorizationCode: 12345, client: {}, expiresAt: new Date(new Date().getTime() / 2), @@ -900,7 +902,7 @@ describe('AuthorizationCodeGrantType integration', () => { }); it('should support non-promises', () => { - const authorizationCode = { + const authorizationCode: any = { authorizationCode: 12345, client: {}, expiresAt: new Date(new Date().getTime() / 2), diff --git a/test/integration/grant-types/client-credentials-grant-type.spec.ts b/test/integration/grant-types/client-credentials-grant-type.spec.ts index 5dd8ab11b..305707f0d 100755 --- a/test/integration/grant-types/client-credentials-grant-type.spec.ts +++ b/test/integration/grant-types/client-credentials-grant-type.spec.ts @@ -1,7 +1,6 @@ import * as should from 'should'; -import { InvalidArgumentError } from '../../../lib/errors/invalid-argument-error'; -import { InvalidGrantError } from '../../../lib/errors/invalid-grant-error'; -import { ClientCredentialsGrantType } from '../../../lib/grant-types/client-credentials-grant-type'; +import { InvalidArgumentError, InvalidGrantError } from '../../../lib/errors'; +import { ClientCredentialsGrantType } from '../../../lib/grant-types'; import { Request } from '../../../lib/request'; /** diff --git a/test/integration/grant-types/implicit-grant-type.spec.ts b/test/integration/grant-types/implicit-grant-type.spec.ts index d439b2ccf..e65758256 100644 --- a/test/integration/grant-types/implicit-grant-type.spec.ts +++ b/test/integration/grant-types/implicit-grant-type.spec.ts @@ -1,6 +1,7 @@ -import { InvalidArgumentError, Request } from 'index'; import * as should from 'should'; -import { ImplicitGrantType } from '../../../lib/grant-types/implicit-grant-type'; +import { InvalidArgumentError } from '../../../lib/errors'; +import { ImplicitGrantType } from '../../../lib/grant-types'; +import { Request } from '../../../lib/request'; /** * Test `ImplicitGrantType` integration. @@ -10,7 +11,7 @@ describe('ImplicitGrantType integration', () => { describe('constructor()', () => { it('should throw an error if `model` is missing', () => { try { - new ImplicitGrantType(); + new ImplicitGrantType({ accessTokenLifetime: 3600 }); should.fail('should.fail', ''); } catch (e) { @@ -23,7 +24,7 @@ describe('ImplicitGrantType integration', () => { try { const model = {}; - new ImplicitGrantType({ model }); + new ImplicitGrantType({ model, accessTokenLifetime: 3600 }); should.fail('should.fail', ''); } catch (e) { @@ -40,7 +41,7 @@ describe('ImplicitGrantType integration', () => { saveToken() {}, }; - new ImplicitGrantType({ model }); + new ImplicitGrantType({ model, accessTokenLifetime: 3600 }); should.fail('should.fail', ''); } catch (e) { @@ -51,7 +52,7 @@ describe('ImplicitGrantType integration', () => { }); describe('handle()', () => { - it('should throw an error if `request` is missing', () => { + it('should throw an error if `request` is missing', async () => { const model = { saveToken() {}, }; @@ -62,8 +63,7 @@ describe('ImplicitGrantType integration', () => { }); try { - grantType.handle(); - + await grantType.handle(); should.fail('should.fail', ''); } catch (e) { e.should.be.an.instanceOf(InvalidArgumentError); @@ -71,7 +71,7 @@ describe('ImplicitGrantType integration', () => { } }); - it('should throw an error if `client` is missing', () => { + it('should throw an error if `client` is missing', async () => { const model = { saveToken() {}, }; @@ -88,7 +88,7 @@ describe('ImplicitGrantType integration', () => { }); try { - grantType.handle(request, undefined); + await grantType.handle(request, undefined); } catch (e) { e.should.be.an.instanceOf(InvalidArgumentError); e.message.should.equal('Missing parameter: `client`'); diff --git a/test/integration/grant-types/password-grant-type.spec.ts b/test/integration/grant-types/password-grant-type.spec.ts index 9c5ea01ee..5cb593e1e 100755 --- a/test/integration/grant-types/password-grant-type.spec.ts +++ b/test/integration/grant-types/password-grant-type.spec.ts @@ -1,8 +1,10 @@ import * as should from 'should'; -import { InvalidArgumentError } from '../../../lib/errors/invalid-argument-error'; -import { InvalidGrantError } from '../../../lib/errors/invalid-grant-error'; -import { InvalidRequestError } from '../../../lib/errors/invalid-request-error'; -import { PasswordGrantType } from '../../../lib/grant-types/password-grant-type'; +import { + InvalidArgumentError, + InvalidGrantError, + InvalidRequestError, +} from '../../../lib/errors'; +import { PasswordGrantType } from '../../../lib/grant-types'; import { Request } from '../../../lib/request'; /** diff --git a/test/integration/grant-types/refresh-token-grant-type.spec.ts b/test/integration/grant-types/refresh-token-grant-type.spec.ts index f66a09aeb..fe40ddf88 100755 --- a/test/integration/grant-types/refresh-token-grant-type.spec.ts +++ b/test/integration/grant-types/refresh-token-grant-type.spec.ts @@ -1,9 +1,11 @@ import * as should from 'should'; -import { InvalidArgumentError } from '../../../lib/errors/invalid-argument-error'; -import { InvalidGrantError } from '../../../lib/errors/invalid-grant-error'; -import { InvalidRequestError } from '../../../lib/errors/invalid-request-error'; -import { ServerError } from '../../../lib/errors/server-error'; -import { RefreshTokenGrantType } from '../../../lib/grant-types/refresh-token-grant-type'; +import { + InvalidArgumentError, + InvalidGrantError, + InvalidRequestError, + ServerError, +} from '../../../lib/errors'; +import { RefreshTokenGrantType } from '../../../lib/grant-types'; import { Request } from '../../../lib/request'; /** diff --git a/test/integration/handlers/authenticate-handler.spec.ts b/test/integration/handlers/authenticate-handler.spec.ts index bc28cf18a..1154e9ab8 100755 --- a/test/integration/handlers/authenticate-handler.spec.ts +++ b/test/integration/handlers/authenticate-handler.spec.ts @@ -1,12 +1,14 @@ import * as should from 'should'; -import { AccessDeniedError } from '../../../lib/errors/access-denied-error'; -import { InsufficientScopeError } from '../../../lib/errors/insufficient-scope-error'; -import { InvalidArgumentError } from '../../../lib/errors/invalid-argument-error'; -import { InvalidRequestError } from '../../../lib/errors/invalid-request-error'; -import { InvalidTokenError } from '../../../lib/errors/invalid-token-error'; -import { ServerError } from '../../../lib/errors/server-error'; -import { UnauthorizedRequestError } from '../../../lib/errors/unauthorized-request-error'; -import { AuthenticateHandler } from '../../../lib/handlers/authenticate-handler'; +import { + AccessDeniedError, + InsufficientScopeError, + InvalidArgumentError, + InvalidRequestError, + InvalidTokenError, + ServerError, + UnauthorizedRequestError, +} from '../../../lib/errors'; +import { AuthenticateHandler } from '../../../lib/handlers'; import { Request } from '../../../lib/request'; import { Response } from '../../../lib/response'; diff --git a/test/integration/handlers/authorize-handler.spec.ts b/test/integration/handlers/authorize-handler.spec.ts index 63d5b11a5..18a42f875 100755 --- a/test/integration/handlers/authorize-handler.spec.ts +++ b/test/integration/handlers/authorize-handler.spec.ts @@ -1,17 +1,18 @@ import * as should from 'should'; import * as url from 'url'; -import { AccessDeniedError } from '../../../lib/errors/access-denied-error'; -import { InvalidArgumentError } from '../../../lib/errors/invalid-argument-error'; -import { InvalidClientError } from '../../../lib/errors/invalid-client-error'; -import { InvalidRequestError } from '../../../lib/errors/invalid-request-error'; -import { InvalidScopeError } from '../../../lib/errors/invalid-scope-error'; -import { ServerError } from '../../../lib/errors/server-error'; -import { UnauthorizedClientError } from '../../../lib/errors/unauthorized-client-error'; -import { AuthenticateHandler } from '../../../lib/handlers/authenticate-handler'; -import { AuthorizeHandler } from '../../../lib/handlers/authorize-handler'; +import { + AccessDeniedError, + InvalidArgumentError, + InvalidClientError, + InvalidRequestError, + InvalidScopeError, + ServerError, + UnauthorizedClientError, +} from '../../../lib/errors'; +import { AuthenticateHandler, AuthorizeHandler } from '../../../lib/handlers'; import { Request } from '../../../lib/request'; import { Response } from '../../../lib/response'; -import { CodeResponseType } from '../../../lib/response-types/code-response-type'; +import { CodeResponseType } from '../../../lib/response-types'; /** * Test `AuthorizeHandler` integration. @@ -19,18 +20,19 @@ import { CodeResponseType } from '../../../lib/response-types/code-response-type describe('AuthorizeHandler integration', () => { describe('constructor()', () => { - it('should throw an error if `options.authorizationCodeLifetime` is missing', () => { - try { - new AuthorizeHandler(); - - should.fail('should.fail', ''); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal( - 'Missing parameter: `authorizationCodeLifetime`', - ); - } - }); + // Move to Code Response Type + // it('should throw an error if `options.authorizationCodeLifetime` is missing', () => { + // try { + // new AuthorizeHandler({ model: {} }); + + // should.fail('should.fail', ''); + // } catch (e) { + // e.should.be.an.instanceOf(InvalidArgumentError); + // e.message.should.equal( + // 'Missing parameter: `authorizationCodeLifetime`', + // ); + // } + // }); it('should throw an error if `options.model` is missing', () => { try { @@ -56,21 +58,22 @@ describe('AuthorizeHandler integration', () => { } }); - it('should throw an error if the model does not implement `saveAuthorizationCode()`', () => { - try { - new AuthorizeHandler({ - authorizationCodeLifetime: 120, - model: { getClient: () => {} }, - }); - - should.fail('should.fail', ''); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal( - 'Invalid argument: model does not implement `saveAuthorizationCode()`', - ); - } - }); + // Move to Code Response Type + // it('should throw an error if the model does not implement `saveAuthorizationCode()`', () => { + // try { + // new AuthorizeHandler({ + // authorizationCodeLifetime: 120, + // model: { getClient: () => {} }, + // }); + + // should.fail('should.fail', ''); + // } catch (e) { + // e.should.be.an.instanceOf(InvalidArgumentError); + // e.message.should.equal( + // 'Invalid argument: model does not implement `saveAuthorizationCode()`', + // ); + // } + // }); it('should throw an error if the model does not implement `getAccessToken()`', () => { const model = { @@ -958,11 +961,11 @@ describe('AuthorizeHandler integration', () => { it('should support promises', async () => { const model = { getAccessToken() {}, - getClient() { - return Promise.resolve({ + async getClient() { + return { grants: ['authorization_code'], redirectUris: ['http://example.com/cb'], - }); + }; }, saveAuthorizationCode() {}, }; @@ -1519,7 +1522,11 @@ describe('AuthorizeHandler integration', () => { authorizationCodeLifetime: 120, model, }); - const responseType = new CodeResponseType(12345); + const responseType = new CodeResponseType({ + authorizationCodeLifetime: 360, + model: { saveAuthorizationCode: () => {} }, + }); + responseType.code = 12345; const redirectUri = handler.buildSuccessRedirectUri( 'http://example.com/cb', responseType, @@ -1541,9 +1548,13 @@ describe('AuthorizeHandler integration', () => { authorizationCodeLifetime: 120, model, }); + const responseType = new CodeResponseType({ + authorizationCodeLifetime: 360, + model: { saveAuthorizationCode: () => {} }, + }); const redirectUri = handler.buildErrorRedirectUri( 'http://example.com/cb', - new CodeResponseType(), + responseType, error, ); @@ -1565,9 +1576,13 @@ describe('AuthorizeHandler integration', () => { authorizationCodeLifetime: 120, model, }); + const responseType = new CodeResponseType({ + authorizationCodeLifetime: 360, + model: { saveAuthorizationCode: () => {} }, + }); const redirectUri = handler.buildErrorRedirectUri( 'http://example.com/cb', - new CodeResponseType(), + responseType, error, ); @@ -1590,10 +1605,14 @@ describe('AuthorizeHandler integration', () => { authorizationCodeLifetime: 120, model, }); + const responseType = new CodeResponseType({ + authorizationCodeLifetime: 360, + model: { saveAuthorizationCode: () => {} }, + }); const response = new Response({ body: {}, headers: {} }); const uri = url.parse('http://example.com/cb', true); - handler.updateResponse(response, uri, new CodeResponseType(), 'foobar'); + handler.updateResponse(response, uri, responseType, 'foobar'); response .get('location') diff --git a/test/integration/handlers/revoke-handler.spec.ts b/test/integration/handlers/revoke-handler.spec.ts index 31cd35c73..71e279333 100644 --- a/test/integration/handlers/revoke-handler.spec.ts +++ b/test/integration/handlers/revoke-handler.spec.ts @@ -1,16 +1,17 @@ +import * as should from 'should'; +import * as util from 'util'; import { AccessDeniedError, InvalidArgumentError, InvalidClientError, InvalidRequestError, InvalidTokenError, - Request, - Response, - RevokeHandler, ServerError, -} from 'index'; -import * as should from 'should'; -import * as util from 'util'; +} from '../../../lib/errors'; +import { RevokeHandler } from '../../../lib/handlers'; +import { Request } from '../../../lib/request'; +import { Response } from '../../../lib/response'; + /** * Test `RevokeHandler` integration. */ @@ -55,7 +56,7 @@ describe('RevokeHandler integration', () => { }); describe('handle()', () => { - it('should throw an error if `request` is missing', () => { + it('should throw an error if `request` is missing', async () => { const model = { getClient() {}, revokeToken() {}, @@ -65,7 +66,7 @@ describe('RevokeHandler integration', () => { const handler: any = new RevokeHandler({ model }); try { - handler.handle(); + await handler.handle(); should.fail('should.fail', ''); } catch (e) { @@ -76,7 +77,7 @@ describe('RevokeHandler integration', () => { } }); - it('should throw an error if `response` is missing', () => { + it('should throw an error if `response` is missing', async () => { const model = { getClient() {}, revokeToken() {}, @@ -92,7 +93,7 @@ describe('RevokeHandler integration', () => { }); try { - handler.handle(request); + await handler.handle(request); should.fail('should.fail', ''); } catch (e) { @@ -327,7 +328,8 @@ describe('RevokeHandler integration', () => { .handle(request, response) .then(should.fail) .catch(e => { - e.should.be.an.instanceOf(InvalidTokenError); + e[0].should.be.an.instanceOf(InvalidTokenError); + e[1].should.be.an.instanceOf(InvalidTokenError); response.body.should.eql({}); response.status.should.equal(200); }); @@ -379,7 +381,7 @@ describe('RevokeHandler integration', () => { }); describe('getClient()', () => { - it('should throw an error if `clientId` is invalid', () => { + it('should throw an error if `clientId` is invalid', async () => { const model = { getClient() {}, revokeToken() {}, @@ -395,7 +397,7 @@ describe('RevokeHandler integration', () => { }); try { - handler.getClient(request); + await handler.getClient(request); should.fail('should.fail', ''); } catch (e) { @@ -404,7 +406,7 @@ describe('RevokeHandler integration', () => { } }); - it('should throw an error if `clientId` is invalid', () => { + it('should throw an error if `clientId` is invalid', async () => { const model = { getClient() {}, revokeToken() {}, @@ -420,7 +422,7 @@ describe('RevokeHandler integration', () => { }); try { - handler.getClient(request); + await handler.getClient(request); should.fail('should.fail', ''); } catch (e) { diff --git a/test/integration/handlers/token-handler.spec.ts b/test/integration/handlers/token-handler.spec.ts index 822a27e33..b1058508b 100755 --- a/test/integration/handlers/token-handler.spec.ts +++ b/test/integration/handlers/token-handler.spec.ts @@ -1,17 +1,19 @@ import * as should from 'should'; import * as util from 'util'; -import { AccessDeniedError } from '../../../lib/errors/access-denied-error'; -import { InvalidArgumentError } from '../../../lib/errors/invalid-argument-error'; -import { InvalidClientError } from '../../../lib/errors/invalid-client-error'; -import { InvalidRequestError } from '../../../lib/errors/invalid-request-error'; -import { ServerError } from '../../../lib/errors/server-error'; -import { UnauthorizedClientError } from '../../../lib/errors/unauthorized-client-error'; -import { UnsupportedGrantTypeError } from '../../../lib/errors/unsupported-grant-type-error'; -import { PasswordGrantType } from '../../../lib/grant-types/password-grant-type'; -import { TokenHandler } from '../../../lib/handlers/token-handler'; +import { + AccessDeniedError, + InvalidArgumentError, + InvalidClientError, + InvalidRequestError, + ServerError, + UnauthorizedClientError, + UnsupportedGrantTypeError, +} from '../../../lib/errors'; +import { PasswordGrantType } from '../../../lib/grant-types'; +import { TokenHandler } from '../../../lib/handlers'; import { Request } from '../../../lib/request'; import { Response } from '../../../lib/response'; -import { BearerTokenType } from '../../../lib/token-types/bearer-token-type'; +import { BearerTokenType } from '../../../lib/token-types'; /** * Test `TokenHandler` integration. @@ -409,7 +411,7 @@ describe('TokenHandler integration', () => { error: 'server_error', error_description: 'Unhandled exception', }); - response.status.should.equal(503); + response.status.should.equal(500); }); }); @@ -525,7 +527,7 @@ describe('TokenHandler integration', () => { }); }); - it('should return custom attributes in a bearer token if the allowExtendedTokenAttributes is set', () => { + it('should return custom attributes in a bearer token if the allowExtendedTokenAttributes is set', async () => { const token = { accessToken: 'foo', client: {}, @@ -572,18 +574,12 @@ describe('TokenHandler integration', () => { }); const response = new Response({ body: {}, headers: {} }); - return handler - .handle(request, response) - .then(() => { - should.exist(response.body.access_token); - should.exist(response.body.refresh_token); - should.exist(response.body.token_type); - should.exist(response.body.scope); - should.exist(response.body.foo); - }) - .catch(() => { - should.fail('should.fail', ''); - }); + await handler.handle(request, response); + should.exist(response.body.access_token); + should.exist(response.body.refresh_token); + should.exist(response.body.token_type); + should.exist(response.body.scope); + should.exist(response.body.foo); }); }); @@ -1156,7 +1152,7 @@ describe('TokenHandler integration', () => { }); it('should throw an error if `grant_type` is unauthorized', async () => { - const client = { grants: ['client_credentials'] }; + const client: any = { grants: ['client_credentials'] }; const model = { getClient() {}, saveToken() {}, @@ -1216,7 +1212,7 @@ describe('TokenHandler integration', () => { describe('with grant_type `authorization_code`', () => { it('should return a token', () => { - const client = { id: 'foobar', grants: ['authorization_code'] }; + const client: any = { id: 'foobar', grants: ['authorization_code'] }; const token = {}; const model = { getAuthorizationCode() { @@ -1258,20 +1254,18 @@ describe('TokenHandler integration', () => { query: {}, }); - return handler - .handleGrantType(request, client) - .then(data => { - data.should.equal(token); - }) - .catch(() => { - should.fail('should.fail', ''); - }); + return handler.handleGrantType(request, client).then(data => { + data.should.equal(token); + }); + // .catch(() => { + // should.fail('should.fail', ''); + // }); }); }); describe('with grant_type `client_credentials`', () => { it('should return a token', () => { - const client = { grants: ['client_credentials'] }; + const client: any = { grants: ['client_credentials'] }; const token = {}; const model = { getClient() {}, @@ -1313,7 +1307,7 @@ describe('TokenHandler integration', () => { describe('with grant_type `password`', () => { it('should return a token', () => { - const client = { grants: ['password'] }; + const client: any = { grants: ['password'] }; const token = {}; const model = { getClient() {}, @@ -1359,7 +1353,7 @@ describe('TokenHandler integration', () => { describe('with grant_type `refresh_token`', () => { it('should return a token', () => { - const client = { grants: ['refresh_token'] }; + const client: any = { grants: ['refresh_token'] }; const token = { accessToken: 'foo', client: {}, user: {} }; const model = { getClient() {}, @@ -1411,7 +1405,7 @@ describe('TokenHandler integration', () => { describe('with custom grant_type', () => { it('should return a token', () => { - const client = { + const client: any = { grants: ['urn:ietf:params:oauth:grant-type:saml2-bearer'], }; const token = {}; @@ -1460,7 +1454,7 @@ describe('TokenHandler integration', () => { describe('getAccessTokenLifetime()', () => { it('should return the client access token lifetime', () => { - const client = { accessTokenLifetime: 60 }; + const client: any = { accessTokenLifetime: 60 }; const model = { getClient() { return client; @@ -1477,7 +1471,7 @@ describe('TokenHandler integration', () => { }); it('should return the default access token lifetime', () => { - const client = {}; + const client: any = {}; const model = { getClient() { return client; diff --git a/test/integration/request.spec.ts b/test/integration/request.spec.ts index 81c08ebd0..c78f8e66d 100755 --- a/test/integration/request.spec.ts +++ b/test/integration/request.spec.ts @@ -1,5 +1,5 @@ import * as should from 'should'; -import { InvalidArgumentError } from '../../lib/errors/invalid-argument-error'; +import { InvalidArgumentError } from '../../lib/errors'; import { Request } from '../../lib/request'; /** diff --git a/test/integration/response-types/code-response-type.spec.ts b/test/integration/response-types/code-response-type.spec.ts index 029f40cde..1824a7f51 100755 --- a/test/integration/response-types/code-response-type.spec.ts +++ b/test/integration/response-types/code-response-type.spec.ts @@ -1,8 +1,8 @@ import * as should from 'should'; import * as sinon from 'sinon'; import * as url from 'url'; -import { InvalidArgumentError } from '../../../lib/errors/invalid-argument-error'; -import { CodeResponseType } from '../../../lib/response-types/code-response-type'; +import { InvalidArgumentError } from '../../../lib/errors'; +import { CodeResponseType } from '../../../lib/response-types'; /** * Test `CodeResponseType` integration. diff --git a/test/integration/response-types/token-response-type.spec.ts b/test/integration/response-types/token-response-type.spec.ts index a959603d5..97847c8a6 100644 --- a/test/integration/response-types/token-response-type.spec.ts +++ b/test/integration/response-types/token-response-type.spec.ts @@ -1,7 +1,7 @@ import * as should from 'should'; import * as url from 'url'; -import { InvalidArgumentError } from '../../../lib/errors/invalid-argument-error'; -import { TokenResponseType } from '../../../lib/response-types/token-response-type'; +import { InvalidArgumentError } from '../../../lib/errors'; +import { TokenResponseType } from '../../../lib/response-types'; /** * Test `TokenResponseType` integration. diff --git a/test/integration/server.spec.ts b/test/integration/server.spec.ts index ff35ddddf..c1a3477a0 100755 --- a/test/integration/server.spec.ts +++ b/test/integration/server.spec.ts @@ -1,9 +1,11 @@ import * as should from 'should'; import * as sinon from 'sinon'; -import { InvalidArgumentError } from '../../lib/errors/invalid-argument-error'; -import { AuthenticateHandler } from '../../lib/handlers/authenticate-handler'; -import { AuthorizeHandler } from '../../lib/handlers/authorize-handler'; -import { TokenHandler } from '../../lib/handlers/token-handler'; +import { InvalidArgumentError } from '../../lib/errors'; +import { + AuthenticateHandler, + AuthorizeHandler, + TokenHandler, +} from '../../lib/handlers'; import { Request } from '../../lib/request'; import { Response } from '../../lib/response'; import { OAuth2Server as Server } from '../../lib/server'; @@ -140,17 +142,18 @@ describe('Server integration', () => { query: { state: 'foobar' }, }); const response = new Response({ body: {}, headers: {} }); - try { - const stub = sinon - .stub(AuthorizeHandler.prototype, 'handle') - .returnsThis(); - const code = await server.authorize(request, response); - code.allowEmptyState.should.be.false(); - code.authorizationCodeLifetime.should.be.equal(300); - stub.restore(); - } catch (error) { - should.fail('should.fail', ''); - } + // try { + const stub = sinon + .stub(AuthorizeHandler.prototype, 'handle') + .returnsThis(); + const code = await server.authorize(request, response); + const options = code.options; + options.allowEmptyState.should.be.false(); + options.authorizationCodeLifetime.should.be.equal(300); + stub.restore(); + // } catch (error) { + // should.fail('should.fail', ''); + // } }); it('should return a promise', () => { diff --git a/test/integration/token-types/bearer-token-type.spec.ts b/test/integration/token-types/bearer-token-type.spec.ts index 25e70d2ff..44ef8ef11 100755 --- a/test/integration/token-types/bearer-token-type.spec.ts +++ b/test/integration/token-types/bearer-token-type.spec.ts @@ -1,6 +1,6 @@ import * as should from 'should'; -import { InvalidArgumentError } from '../../../lib/errors/invalid-argument-error'; -import { BearerTokenType } from '../../../lib/token-types/bearer-token-type'; +import { InvalidArgumentError } from '../../../lib/errors'; +import { BearerTokenType } from '../../../lib/token-types'; /** * Test `BearerTokenType` integration. diff --git a/test/unit/grant-types/abstract-grant-type.spec.ts b/test/unit/grant-types/abstract-grant-type.spec.ts index 1e6893c20..12ab50977 100755 --- a/test/unit/grant-types/abstract-grant-type.spec.ts +++ b/test/unit/grant-types/abstract-grant-type.spec.ts @@ -1,6 +1,6 @@ import * as should from 'should'; import * as sinon from 'sinon'; -import { AbstractGrantType } from '../../../lib/grant-types/abstract-grant-type'; +import { AbstractGrantType } from '../../../lib/grant-types'; /** * Test `AbstractGrantType`. diff --git a/test/unit/grant-types/authorization-code-grant-type.spec.ts b/test/unit/grant-types/authorization-code-grant-type.spec.ts index b50044dc6..66ad6a39b 100755 --- a/test/unit/grant-types/authorization-code-grant-type.spec.ts +++ b/test/unit/grant-types/authorization-code-grant-type.spec.ts @@ -1,6 +1,6 @@ import * as should from 'should'; import * as sinon from 'sinon'; -import { AuthorizationCodeGrantType } from '../../../lib/grant-types/authorization-code-grant-type'; +import { AuthorizationCodeGrantType } from '../../../lib/grant-types'; import { Request } from '../../../lib/request'; /** @@ -55,7 +55,7 @@ describe('AuthorizationCodeGrantType', () => { accessTokenLifetime: 120, model, }); - const authorizationCode = {}; + const authorizationCode: any = {}; try { await handler.revokeAuthorizationCode(authorizationCode); diff --git a/test/unit/grant-types/client-credentials-grant-type.spec.ts b/test/unit/grant-types/client-credentials-grant-type.spec.ts index b0585d609..693c7d669 100755 --- a/test/unit/grant-types/client-credentials-grant-type.spec.ts +++ b/test/unit/grant-types/client-credentials-grant-type.spec.ts @@ -1,6 +1,6 @@ import * as should from 'should'; import * as sinon from 'sinon'; -import { ClientCredentialsGrantType } from '../../../lib/grant-types/client-credentials-grant-type'; +import { ClientCredentialsGrantType } from '../../../lib/grant-types'; /** * Test `ClientCredentialsGrantType`. diff --git a/test/unit/grant-types/implict-grant-type.spec.ts b/test/unit/grant-types/implict-grant-type.spec.ts index f6a34a6eb..df41a9488 100644 --- a/test/unit/grant-types/implict-grant-type.spec.ts +++ b/test/unit/grant-types/implict-grant-type.spec.ts @@ -1,6 +1,6 @@ import * as should from 'should'; import * as sinon from 'sinon'; -import { ImplicitGrantType } from '../../../lib/grant-types/implicit-grant-type'; +import { ImplicitGrantType } from '../../../lib/grant-types'; /** * Test `ImplicitGrantType`. */ diff --git a/test/unit/grant-types/password-grant-type.spec.ts b/test/unit/grant-types/password-grant-type.spec.ts index 7ef7b24a5..420a232a8 100755 --- a/test/unit/grant-types/password-grant-type.spec.ts +++ b/test/unit/grant-types/password-grant-type.spec.ts @@ -1,6 +1,6 @@ import * as should from 'should'; import * as sinon from 'sinon'; -import { PasswordGrantType } from '../../../lib/grant-types/password-grant-type'; +import { PasswordGrantType } from '../../../lib/grant-types'; import { Request } from '../../../lib/request'; /** diff --git a/test/unit/grant-types/refresh-token-grant-type.spec.ts b/test/unit/grant-types/refresh-token-grant-type.spec.ts index 7bd821b02..40d1d0ada 100755 --- a/test/unit/grant-types/refresh-token-grant-type.spec.ts +++ b/test/unit/grant-types/refresh-token-grant-type.spec.ts @@ -1,6 +1,6 @@ import * as should from 'should'; import * as sinon from 'sinon'; -import { RefreshTokenGrantType } from '../../../lib/grant-types/refresh-token-grant-type'; +import { RefreshTokenGrantType } from '../../../lib/grant-types'; import { Request } from '../../../lib/request'; /** @@ -191,10 +191,10 @@ describe('RefreshTokenGrantType', () => { model, }); - sinon.stub(handler, 'generateAccessToken').returns('foo' as any); - sinon.stub(handler, 'generateRefreshToken').returns('bar' as any); - sinon.stub(handler, 'getAccessTokenExpiresAt').returns('biz' as any); - sinon.stub(handler, 'getRefreshTokenExpiresAt').returns('baz' as any); + sinon.stub(handler, 'generateAccessToken').returns('foo'); + sinon.stub(handler, 'generateRefreshToken').returns('bar'); + sinon.stub(handler, 'getAccessTokenExpiresAt').returns('biz'); + sinon.stub(handler, 'getRefreshTokenExpiresAt').returns('baz'); return handler .saveToken(user, client, 'foobar') diff --git a/test/unit/handlers/authenticate-handler.spec.ts b/test/unit/handlers/authenticate-handler.spec.ts index a1ba57e99..21672e4f1 100755 --- a/test/unit/handlers/authenticate-handler.spec.ts +++ b/test/unit/handlers/authenticate-handler.spec.ts @@ -1,7 +1,7 @@ import * as should from 'should'; import * as sinon from 'sinon'; -import { ServerError } from '../../../lib/errors/server-error'; -import { AuthenticateHandler } from '../../../lib/handlers/authenticate-handler'; +import { ServerError } from '../../../lib/errors'; +import { AuthenticateHandler } from '../../../lib/handlers'; import { Request } from '../../../lib/request'; /** diff --git a/test/unit/handlers/authorize-handler.spec.ts b/test/unit/handlers/authorize-handler.spec.ts index 34254fdfc..508b239e7 100755 --- a/test/unit/handlers/authorize-handler.spec.ts +++ b/test/unit/handlers/authorize-handler.spec.ts @@ -1,6 +1,6 @@ import * as should from 'should'; import * as sinon from 'sinon'; -import { AuthorizeHandler } from '../../../lib/handlers/authorize-handler'; +import { AuthorizeHandler } from '../../../lib/handlers'; import { Request } from '../../../lib/request'; import { Response } from '../../../lib/response'; diff --git a/test/unit/handlers/revoke-handlers.ts b/test/unit/handlers/revoke-handlers.ts index 72fdbec3a..007a38dba 100644 --- a/test/unit/handlers/revoke-handlers.ts +++ b/test/unit/handlers/revoke-handlers.ts @@ -1,6 +1,7 @@ -import { Request, RevokeHandler } from 'index'; import * as should from 'should'; import * as sinon from 'sinon'; +import { RevokeHandler } from '../../../lib/handlers'; +import { Request } from '../../../lib/request'; /** * Test `RevokeHandler`. diff --git a/test/unit/handlers/token-handler.spec.ts b/test/unit/handlers/token-handler.spec.ts index fab47fef9..9316ad9fd 100755 --- a/test/unit/handlers/token-handler.spec.ts +++ b/test/unit/handlers/token-handler.spec.ts @@ -1,6 +1,6 @@ import * as should from 'should'; import * as sinon from 'sinon'; -import { TokenHandler } from '../../../lib/handlers/token-handler'; +import { TokenHandler } from '../../../lib/handlers'; import { Request } from '../../../lib/request'; /** diff --git a/test/unit/server.spec.ts b/test/unit/server.spec.ts index 58d88de62..2ce3b4f7d 100755 --- a/test/unit/server.spec.ts +++ b/test/unit/server.spec.ts @@ -1,7 +1,9 @@ import * as sinon from 'sinon'; -import { AuthenticateHandler } from '../../lib/handlers/authenticate-handler'; -import { AuthorizeHandler } from '../../lib/handlers/authorize-handler'; -import { TokenHandler } from '../../lib/handlers/token-handler'; +import { + AuthenticateHandler, + AuthorizeHandler, + TokenHandler, +} from '../../lib/handlers'; import { OAuth2Server as Server } from '../../lib/server'; const Authenticate: any = AuthenticateHandler; diff --git a/tslint.json b/tslint.json index 76a96ef8f..0091ad1da 100755 --- a/tslint.json +++ b/tslint.json @@ -1,28 +1,29 @@ -{ - // "defaultSeverity": "error", - "extends": ["tslint:recommended", "tslint:all", "tslint:latest"], - "jsRules": { - "no-unused-expression": true - }, - "rules": { - "arrow-parens": false, - "completed-docs": false, - "file-name-casing": [true, "kebab-case"], - "no-object-literal-type-assertion": [false], - "interface-name": [false], - "max-line-length": [true, 120], - "member-access": [false], - "member-ordering": [false], - "no-default-export": [false], - "prefer-function-over-method": [false], - "no-magic-numbers": [false], - "newline-per-chained-call": false, - "no-unsafe-any": false, - "object-literal-sort-keys": false, - "ordered-imports": [false], - "quotemark": [true, "single"], - "semicolon": [true, "always", "ignore-bound-class-methods"], - "strict-boolean-expressions": [false] - }, - "rulesDirectory": [] -} +{ + // "defaultSeverity": "error", + "extends": ["tslint:recommended", "tslint:all", "tslint:latest"], + "jsRules": { + "no-unused-expression": true + }, + "rules": { + "arrow-parens": false, + "completed-docs": false, + "file-name-casing": [true, "kebab-case"], + "no-object-literal-type-assertion": [false], + "interface-name": [false], + "max-line-length": [true, 120], + "member-access": [false], + "no-boolean-literal-compare": [false], + "member-ordering": [false], + "no-default-export": [false], + "prefer-function-over-method": [false], + "no-magic-numbers": [false], + "newline-per-chained-call": false, + "no-unsafe-any": false, + "object-literal-sort-keys": false, + "ordered-imports": [false], + "quotemark": [true, "single"], + "semicolon": [true, "always", "ignore-bound-class-methods"], + "strict-boolean-expressions": [false] + }, + "rulesDirectory": [] +} From 28bdd3c25b1fb84c2f2057d542b33468fb361bc2 Mon Sep 17 00:00:00 2001 From: Ankit Date: Fri, 10 May 2019 10:46:07 +0530 Subject: [PATCH 11/30] updated-todos-and-file-folder-permissions --- TODO | 5 ++++- package.json | 7 ++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/TODO b/TODO index f781a9156..205d0697f 100644 --- a/TODO +++ b/TODO @@ -2,4 +2,7 @@ Todo: ✔ Add a todo ✔ A Basic Rewrite of library in Typescript - ☐ A Basic Rewrite of tests in Typescript @started + ✔ A Basic Rewrite of tests in Typescript + ☐ Add examples for nestjs, expressjs, koa and others + ☐ Add Migration guide + ☐ Review all Docs diff --git a/package.json b/package.json index 2f22624b3..efa9b3be3 100755 --- a/package.json +++ b/package.json @@ -61,20 +61,17 @@ }, "license": "MIT", "engines": { - "node": ">=8.0" + "node": ">=8.10" }, "scripts": { "lint": "tslint -p tsconfig.build.json -c tslint.json", "lint:all": "tslint -p tsconfig.json -c tslint.json", - "pretest": "npx jshint --config ./.jshintrc lib test", "build": "npx tsc -p tsconfig.json", "build:clean": "npx shx rm -rf ./dist", "build:prod": "npx npm-run-all build:clean build:prod:*", "build:prod:src": "npx tsc -p tsconfig.build.json", "build:prod:package": "node ./scripts/build-prod.js", - "test": "NODE_ENV=test npx mocha 'test/**/*_test.js'", - "test2": "npx mocha 'test/**/*.spec.ts' --config 'test/.mocharc.jsonc'", - "test-debug": "NODE_ENV=test npx mocha --inspect --debug-brk 'test/**/*_test.js'" + "test": "npx mocha 'test/**/*.spec.ts' --config 'test/.mocharc.jsonc'" }, "repository": { "type": "git", From c13e9ea8fd07ab371dd49b6020bc0f231c5f0d55 Mon Sep 17 00:00:00 2001 From: Ankit Date: Fri, 10 May 2019 11:13:02 +0530 Subject: [PATCH 12/30] fix-imports --- lib/grant-types/client-credentials-grant-type.ts | 3 +-- lib/interfaces/authorization-code.interface.ts | 3 +-- lib/interfaces/model.interface.ts | 6 +----- lib/interfaces/refresh-token.interface.ts | 3 +-- lib/interfaces/token.interface.ts | 3 +-- lib/response-types/token-response-type.ts | 6 ++---- 6 files changed, 7 insertions(+), 17 deletions(-) diff --git a/lib/grant-types/client-credentials-grant-type.ts b/lib/grant-types/client-credentials-grant-type.ts index 3bf2258ea..cce681661 100755 --- a/lib/grant-types/client-credentials-grant-type.ts +++ b/lib/grant-types/client-credentials-grant-type.ts @@ -1,7 +1,6 @@ import { AbstractGrantType } from '.'; import { InvalidArgumentError, InvalidGrantError } from '../errors'; -import { Client, User } from '../interfaces'; -import { Token } from '../interfaces/token.interface'; +import { Client, Token, User } from '../interfaces'; import { Request } from '../request'; export class ClientCredentialsGrantType extends AbstractGrantType { diff --git a/lib/interfaces/authorization-code.interface.ts b/lib/interfaces/authorization-code.interface.ts index 20e72eccc..a1f781607 100644 --- a/lib/interfaces/authorization-code.interface.ts +++ b/lib/interfaces/authorization-code.interface.ts @@ -1,5 +1,4 @@ -import { Client } from './client.interface'; -import { User } from './user.interface'; +import { Client, User } from '.'; /** * An interface representing the authorization code and associated data. diff --git a/lib/interfaces/model.interface.ts b/lib/interfaces/model.interface.ts index bcecc4e10..a1166e681 100644 --- a/lib/interfaces/model.interface.ts +++ b/lib/interfaces/model.interface.ts @@ -1,8 +1,4 @@ -import { AuthorizationCode } from './authorization-code.interface'; -import { Client } from './client.interface'; -import { RefreshToken } from './refresh-token.interface'; -import { Token } from './token.interface'; -import { User } from './user.interface'; +import { AuthorizationCode, Client, RefreshToken, Token, User } from '.'; export interface BaseModel { /** diff --git a/lib/interfaces/refresh-token.interface.ts b/lib/interfaces/refresh-token.interface.ts index 47d49ccfc..71801f87d 100644 --- a/lib/interfaces/refresh-token.interface.ts +++ b/lib/interfaces/refresh-token.interface.ts @@ -1,5 +1,4 @@ -import { Client } from './client.interface'; -import { User } from './user.interface'; +import { Client, User } from '.'; /** * An interface representing the refresh token and associated data. diff --git a/lib/interfaces/token.interface.ts b/lib/interfaces/token.interface.ts index afa3ccd80..ec696e687 100644 --- a/lib/interfaces/token.interface.ts +++ b/lib/interfaces/token.interface.ts @@ -1,5 +1,4 @@ -import { Client } from './client.interface'; -import { User } from './user.interface'; +import { Client, User } from '.'; /** * An interface representing the token(s) and associated data. diff --git a/lib/response-types/token-response-type.ts b/lib/response-types/token-response-type.ts index c2753b05b..8901a3228 100755 --- a/lib/response-types/token-response-type.ts +++ b/lib/response-types/token-response-type.ts @@ -1,8 +1,6 @@ -import { InvalidArgumentError } from '../errors/invalid-argument-error'; +import { InvalidArgumentError } from '../errors'; import { ImplicitGrantType } from '../grant-types'; -import { Client } from '../interfaces/client.interface'; -import { Model } from '../interfaces/model.interface'; -import { User } from '../interfaces/user.interface'; +import { Client, Model, User } from '../interfaces'; import { Request } from '../request'; export class TokenResponseType { From 6146cc7bf24af752837272b180eb647fa3b02a16 Mon Sep 17 00:00:00 2001 From: Ankit Date: Fri, 10 May 2019 16:50:36 +0530 Subject: [PATCH 13/30] remove-node-6-support --- .travis.yml | 3 --- CHANGELOG.md | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 045099f9c..0235c3573 100755 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,7 @@ language: node_js node_js: - - 6 - - 7 - 8 - - 9 - 10 sudo: false diff --git a/CHANGELOG.md b/CHANGELOG.md index a9e7df8ec..653c6c922 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## Changelog +### 5.0.0 +* BREAKING: Remove support for node v6 + ### 4.0.0 * BREAKING: Set server_error Code to 500 * BREAKING: Remove support for node v4 From 038c90a99573d78b32d376b1a9f3761382bba781 Mon Sep 17 00:00:00 2001 From: Ankit Date: Sat, 18 May 2019 16:22:33 +0530 Subject: [PATCH 14/30] updated-codes-and-changelogs --- CHANGELOG.md | 5 ++ lib/errors/oauth-error.ts | 2 +- lib/errors/server-error.ts | 2 +- .../authorization-code-grant-type.ts | 20 ++---- .../client-credentials-grant-type.ts | 12 +--- lib/grant-types/implicit-grant-type.ts | 14 +--- lib/grant-types/password-grant-type.ts | 20 ++---- lib/grant-types/refresh-token-grant-type.ts | 17 ++--- lib/handlers/authenticate-handler.ts | 4 ++ lib/handlers/authorize-handler.ts | 7 +- lib/handlers/token-handler.ts | 4 ++ lib/interfaces/model.interface.ts | 2 + lib/utils/fn.ts | 12 +++- package-lock.json | 12 ++-- package.json | 4 +- test/assertions.js | 2 - .../authorization-code-grant-type.spec.ts | 8 +-- .../grant-types/implict-grant-type.spec.ts | 4 +- test/unit/handlers/authorize-handler.spec.ts | 72 +++++++++---------- ...ke-handlers.ts => revoke-handlers.spec.ts} | 0 20 files changed, 98 insertions(+), 125 deletions(-) rename test/unit/handlers/{revoke-handlers.ts => revoke-handlers.spec.ts} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 653c6c922..889ae1df8 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ### 5.0.0 * BREAKING: Remove support for node v6 +* BREAKING: Remove support for callbacks only support native Promises +* new: Rewrote in Typescript +* new: Switch from Eslint to Tslint +* new: added .vscode folder for recommend extensions and recommend setting required for development +* new: added suitable Typescript Interfaces for various Objects ### 4.0.0 * BREAKING: Set server_error Code to 500 diff --git a/lib/errors/oauth-error.ts b/lib/errors/oauth-error.ts index 30151aa08..34211b23e 100755 --- a/lib/errors/oauth-error.ts +++ b/lib/errors/oauth-error.ts @@ -30,6 +30,6 @@ export class OAuthError extends Error { this[key] = props[key]; } } - // Error.captureStackTrace(this, OAuthError); + Error.captureStackTrace(this, OAuthError); } } diff --git a/lib/errors/server-error.ts b/lib/errors/server-error.ts index 776b05e94..68db41ef4 100755 --- a/lib/errors/server-error.ts +++ b/lib/errors/server-error.ts @@ -1,7 +1,7 @@ import { OAuthError } from './oauth-error'; /** - * Constructor. + * ServerError * * "The authorization server encountered an unexpected condition that prevented it from fulfilling the request." * diff --git a/lib/grant-types/authorization-code-grant-type.ts b/lib/grant-types/authorization-code-grant-type.ts index 5f1e48b9b..cecdc159f 100755 --- a/lib/grant-types/authorization-code-grant-type.ts +++ b/lib/grant-types/authorization-code-grant-type.ts @@ -182,21 +182,11 @@ export class AuthorizationCodeGrantType extends AbstractGrantType { authorizationCode: string, scope: string, ) { - const fns = [ - this.validateScope(user, client, scope), - this.generateAccessToken(client, user, scope), - this.generateRefreshToken(client, user, scope), - this.getAccessTokenExpiresAt(), - this.getRefreshTokenExpiresAt(), - ]; - - const [ - accessScope, - accessToken, - refreshToken, - accessTokenExpiresAt, - refreshTokenExpiresAt, - ] = await Promise.all(fns as any); + const accessScope = await this.validateScope(user, client, scope); + const accessToken = await this.generateAccessToken(client, user, scope); + const refreshToken = await this.generateRefreshToken(client, user, scope); + const accessTokenExpiresAt = this.getAccessTokenExpiresAt(); + const refreshTokenExpiresAt = this.getRefreshTokenExpiresAt(); const token: Token = { accessToken, diff --git a/lib/grant-types/client-credentials-grant-type.ts b/lib/grant-types/client-credentials-grant-type.ts index cce681661..80736bbac 100755 --- a/lib/grant-types/client-credentials-grant-type.ts +++ b/lib/grant-types/client-credentials-grant-type.ts @@ -64,15 +64,9 @@ export class ClientCredentialsGrantType extends AbstractGrantType { */ async saveToken(user: User, client: Client, scope: string) { - const fns = [ - this.validateScope(user, client, scope), - this.generateAccessToken(client, user, scope), - this.getAccessTokenExpiresAt(), - ]; - - const [accessScope, accessToken, accessTokenExpiresAt] = await Promise.all( - fns as any, - ); + const accessScope = await this.validateScope(user, client, scope); + const accessToken = await this.generateAccessToken(client, user, scope); + const accessTokenExpiresAt = this.getAccessTokenExpiresAt(); const token = { accessToken, diff --git a/lib/grant-types/implicit-grant-type.ts b/lib/grant-types/implicit-grant-type.ts index dba456d97..a37670482 100644 --- a/lib/grant-types/implicit-grant-type.ts +++ b/lib/grant-types/implicit-grant-type.ts @@ -48,17 +48,9 @@ export class ImplicitGrantType extends AbstractGrantType { */ async saveToken(user: User, client: Client, scope: string) { - const fns = [ - this.validateScope(user, client, scope), - this.generateAccessToken(client, user, scope), - this.getAccessTokenExpiresAt(), - ]; - - const [ - validatedScope, - accessToken, - accessTokenExpiresAt, - ] = await Promise.all(fns as any); + const validatedScope = await this.validateScope(user, client, scope); + const accessToken = await this.generateAccessToken(client, user, scope); + const accessTokenExpiresAt = this.getAccessTokenExpiresAt(); const token = { accessToken, diff --git a/lib/grant-types/password-grant-type.ts b/lib/grant-types/password-grant-type.ts index f0c9dc9f7..ca07b06ed 100755 --- a/lib/grant-types/password-grant-type.ts +++ b/lib/grant-types/password-grant-type.ts @@ -89,21 +89,11 @@ export class PasswordGrantType extends AbstractGrantType { */ async saveToken(user: User, client: Client, scope: string) { - const fns = [ - this.validateScope(user, client, scope), - this.generateAccessToken(client, user, scope), - this.generateRefreshToken(client, user, scope), - this.getAccessTokenExpiresAt(), - this.getRefreshTokenExpiresAt(), - ]; - - const [ - accessScope, - accessToken, - refreshToken, - accessTokenExpiresAt, - refreshTokenExpiresAt, - ] = await Promise.all(fns as any); + const accessScope = await this.validateScope(user, client, scope); + const accessToken = await this.generateAccessToken(client, user, scope); + const refreshToken = await this.generateRefreshToken(client, user, scope); + const accessTokenExpiresAt = this.getAccessTokenExpiresAt(); + const refreshTokenExpiresAt = this.getRefreshTokenExpiresAt(); const token = { accessToken, diff --git a/lib/grant-types/refresh-token-grant-type.ts b/lib/grant-types/refresh-token-grant-type.ts index 2137d7eb4..09d16e56e 100755 --- a/lib/grant-types/refresh-token-grant-type.ts +++ b/lib/grant-types/refresh-token-grant-type.ts @@ -135,19 +135,10 @@ export class RefreshTokenGrantType extends AbstractGrantType { */ async saveToken(user: User, client: Client, scope: string) { - const fns = [ - this.generateAccessToken(client, user, scope), - this.generateRefreshToken(client, user, scope), - this.getAccessTokenExpiresAt(), - this.getRefreshTokenExpiresAt(), - ]; - - const [ - accessToken, - refreshToken, - accessTokenExpiresAt, - refreshTokenExpiresAt, - ] = await Promise.all(fns as any); + const accessToken = await this.generateAccessToken(client, user, scope); + const refreshToken = await this.generateRefreshToken(client, user, scope); + const accessTokenExpiresAt = this.getAccessTokenExpiresAt(); + const refreshTokenExpiresAt = this.getRefreshTokenExpiresAt(); const token: any = { accessToken, diff --git a/lib/handlers/authenticate-handler.ts b/lib/handlers/authenticate-handler.ts index d62a1a699..c85c6b978 100755 --- a/lib/handlers/authenticate-handler.ts +++ b/lib/handlers/authenticate-handler.ts @@ -70,6 +70,10 @@ export class AuthenticateHandler { 'Invalid argument: `response` must be an instance of Response', ); } + + // Extend model object with request + this.model.request = request; + try { let token = await this.getTokenFromRequest(request); token = await this.getAccessToken(token); diff --git a/lib/handlers/authorize-handler.ts b/lib/handlers/authorize-handler.ts index c3eee1444..93d67a6e9 100755 --- a/lib/handlers/authorize-handler.ts +++ b/lib/handlers/authorize-handler.ts @@ -84,11 +84,10 @@ export class AuthorizeHandler { } // Extend model object with request - // this.model.request = request; + this.model.request = request; - const fns = [this.getClient(request), this.getUser(request, response)]; - - const [client, user] = await Promise.all(fns); + const client = await this.getClient(request); + const user = await this.getUser(request, response); let scope: string; let state: string; diff --git a/lib/handlers/token-handler.ts b/lib/handlers/token-handler.ts index e5e448ed2..a0c81ddf1 100755 --- a/lib/handlers/token-handler.ts +++ b/lib/handlers/token-handler.ts @@ -101,6 +101,10 @@ export class TokenHandler { 'Invalid request: content must be application/x-www-form-urlencoded', ); } + + // Extend model object with request + this.model.request = request; + try { const client = await this.getClient(request, response); const data = await this.handleGrantType(request, client); diff --git a/lib/interfaces/model.interface.ts b/lib/interfaces/model.interface.ts index a1166e681..14e880e67 100644 --- a/lib/interfaces/model.interface.ts +++ b/lib/interfaces/model.interface.ts @@ -1,6 +1,8 @@ import { AuthorizationCode, Client, RefreshToken, Token, User } from '.'; +import { Request } from '../request'; export interface BaseModel { + request: Request; /** * Invoked to generate a new access token. * diff --git a/lib/utils/fn.ts b/lib/utils/fn.ts index 6a8a076cb..ac5200c4d 100644 --- a/lib/utils/fn.ts +++ b/lib/utils/fn.ts @@ -4,7 +4,17 @@ const reverser = (promise: Promise) => promise.then(v => Promise.reject(v), identity); export const oneSuccess = (promises: Array>) => - Promise.all(promises.map(reverser)).then(e => Promise.reject(e), identity); + Promise.all(promises.map(reverser)).then( + e => Promise.reject(AggregateError.from(e)), + identity, + ); export const hasOwnProperty = (o: any, k: string) => Object.prototype.hasOwnProperty.call(o, k); + +export class AggregateError extends Array implements Error { + name = 'AggregateError'; + get message() { + return this.map(e => e.message).join('\n'); + } +} diff --git a/package-lock.json b/package-lock.json index 0c0d4eadb..7517e7c09 100755 --- a/package-lock.json +++ b/package-lock.json @@ -70,9 +70,9 @@ } }, "@types/lodash": { - "version": "4.14.123", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.123.tgz", - "integrity": "sha512-pQvPkc4Nltyx7G1Ww45OjVqUsJP4UsZm+GWJpigXgkikZqJgRm4c48g027o6tdgubWHwFRF15iFd+Y4Pmqv6+Q==", + "version": "4.14.129", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.129.tgz", + "integrity": "sha512-oYaV0eSlnOacOr7i4X1FFdH8ttSlb57gu3I9MuStIv2CYkISEY84dNHYsC3bF6sNH7qYcu1BtVrCtQ8Q4KPTfQ==", "dev": true }, "@types/mocha": { @@ -82,9 +82,9 @@ "dev": true }, "@types/node": { - "version": "11.13.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.8.tgz", - "integrity": "sha512-szA3x/3miL90ZJxUCzx9haNbK5/zmPieGraZEe4WI+3srN0eGLiT22NXeMHmyhNEopn+IrxqMc7wdVwvPl8meg==", + "version": "11.13.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.11.tgz", + "integrity": "sha512-blLeR+KIy26km1OU8yTLUlSyVCOvT6+wPq/77tIA+uSHHa4yYQosn+bbaJqPtWId0wjVClUtD7aXzDbZeKWqig==", "dev": true }, "@types/sinon": { diff --git a/package.json b/package.json index d72aa25f6..58333981c 100755 --- a/package.json +++ b/package.json @@ -52,9 +52,9 @@ }, "devDependencies": { "@types/basic-auth": "^1.1.2", - "@types/lodash": "^4.14.123", + "@types/lodash": "^4.14.129", "@types/mocha": "^5.2.6", - "@types/node": "^11.13.8", + "@types/node": "^11.13.11", "@types/sinon": "^7.0.11", "@types/statuses": "^1.5.0", "@types/type-is": "^1.6.2", diff --git a/test/assertions.js b/test/assertions.js index d372f6674..f057cdeb5 100755 --- a/test/assertions.js +++ b/test/assertions.js @@ -1,5 +1,3 @@ -const from = require('should'); - /** * SHA-1 assertion. */ diff --git a/test/unit/grant-types/authorization-code-grant-type.spec.ts b/test/unit/grant-types/authorization-code-grant-type.spec.ts index 66ad6a39b..00af87de8 100755 --- a/test/unit/grant-types/authorization-code-grant-type.spec.ts +++ b/test/unit/grant-types/authorization-code-grant-type.spec.ts @@ -92,12 +92,8 @@ describe('AuthorizationCodeGrantType', () => { sinon .stub(handler, 'generateRefreshToken') .returns(Promise.resolve('bar')); - sinon - .stub(handler, 'getAccessTokenExpiresAt') - .returns(Promise.resolve('biz') as any); - sinon - .stub(handler, 'getRefreshTokenExpiresAt') - .returns(Promise.resolve('baz') as any); + sinon.stub(handler, 'getAccessTokenExpiresAt').returns('biz' as any); + sinon.stub(handler, 'getRefreshTokenExpiresAt').returns('baz' as any); try { await handler.saveToken(user, client, 'foobar', 'foobiz'); model.saveToken.callCount.should.equal(1); diff --git a/test/unit/grant-types/implict-grant-type.spec.ts b/test/unit/grant-types/implict-grant-type.spec.ts index df41a9488..504f693e3 100644 --- a/test/unit/grant-types/implict-grant-type.spec.ts +++ b/test/unit/grant-types/implict-grant-type.spec.ts @@ -23,9 +23,7 @@ describe('ImplicitGrantType', () => { sinon .stub(handler, 'generateAccessToken') .returns(Promise.resolve('foobar-token')); - sinon - .stub(handler, 'getAccessTokenExpiresAt') - .returns(Promise.resolve('foo-1234')); + sinon.stub(handler, 'getAccessTokenExpiresAt').returns('foo-1234'); return handler .saveToken(user, client, 'foobar') diff --git a/test/unit/handlers/authorize-handler.spec.ts b/test/unit/handlers/authorize-handler.spec.ts index 508b239e7..ebae522c9 100755 --- a/test/unit/handlers/authorize-handler.spec.ts +++ b/test/unit/handlers/authorize-handler.spec.ts @@ -101,40 +101,40 @@ describe('AuthorizeHandler', () => { }); }); - describe('saveAuthorizationCode()', () => { - // it('should call `model.saveAuthorizationCode()`', () => { - // const model = { - // getAccessToken() {}, - // getClient() {}, - // saveAuthorizationCode: sinon.stub().returns({}), - // }; - // const handler = new AuthorizeHandler({ - // authorizationCodeLifetime: 120, - // model, - // }); - // return handler - // .saveAuthorizationCode( - // 'foo', - // 'bar' as any, - // 'qux', - // 'biz' as any, - // 'baz', - // 'boz' as any, - // ) - // .then(() => { - // model.saveAuthorizationCode.callCount.should.equal(1); - // model.saveAuthorizationCode.firstCall.args.should.have.length(3); - // model.saveAuthorizationCode.firstCall.args[0].should.eql({ - // authorizationCode: 'foo', - // expiresAt: 'bar', - // redirectUri: 'baz', - // scope: 'qux', - // }); - // model.saveAuthorizationCode.firstCall.args[1].should.equal('biz'); - // model.saveAuthorizationCode.firstCall.args[2].should.equal('boz'); - // model.saveAuthorizationCode.firstCall.thisValue.should.equal(model); - // }) - // .catch(() => should.fail('should.fail', '')); - // }); - }); + // describe('saveAuthorizationCode()', () => { + // it('should call `model.saveAuthorizationCode()`', () => { + // const model = { + // getAccessToken() {}, + // getClient() {}, + // saveAuthorizationCode: sinon.stub().returns({}), + // }; + // const handler = new AuthorizeHandler({ + // authorizationCodeLifetime: 120, + // model, + // }); + // return handler + // .saveAuthorizationCode( + // 'foo', + // 'bar' as any, + // 'qux', + // 'biz' as any, + // 'baz', + // 'boz' as any, + // ) + // .then(() => { + // model.saveAuthorizationCode.callCount.should.equal(1); + // model.saveAuthorizationCode.firstCall.args.should.have.length(3); + // model.saveAuthorizationCode.firstCall.args[0].should.eql({ + // authorizationCode: 'foo', + // expiresAt: 'bar', + // redirectUri: 'baz', + // scope: 'qux', + // }); + // model.saveAuthorizationCode.firstCall.args[1].should.equal('biz'); + // model.saveAuthorizationCode.firstCall.args[2].should.equal('boz'); + // model.saveAuthorizationCode.firstCall.thisValue.should.equal(model); + // }) + // .catch(() => should.fail('should.fail', '')); + // }); + // }); }); diff --git a/test/unit/handlers/revoke-handlers.ts b/test/unit/handlers/revoke-handlers.spec.ts similarity index 100% rename from test/unit/handlers/revoke-handlers.ts rename to test/unit/handlers/revoke-handlers.spec.ts From 1aa302ef2ddc71e5a40e2743aaa1f46e92c1e158 Mon Sep 17 00:00:00 2001 From: Ankit Date: Thu, 6 Jun 2019 19:05:48 +0530 Subject: [PATCH 15/30] dependencies-updated --- lib/handlers/token-handler.ts | 7 ++-- lib/models/index.ts | 1 + lib/models/token-model.ts | 6 ++-- package-lock.json | 54 ++++++++++++++++------------ package.json | 14 ++++---- test/unit/models/token-model.spec.ts | 2 +- 6 files changed, 45 insertions(+), 39 deletions(-) create mode 100644 lib/models/index.ts diff --git a/lib/handlers/token-handler.ts b/lib/handlers/token-handler.ts index a0c81ddf1..286a1b41a 100755 --- a/lib/handlers/token-handler.ts +++ b/lib/handlers/token-handler.ts @@ -14,12 +14,11 @@ import { PasswordGrantType, RefreshTokenGrantType, } from '../grant-types'; -import { Model } from '../interfaces'; -import { Client } from '../interfaces/client.interface'; -import { TokenModel } from '../models/token-model'; +import { Client, Model } from '../interfaces'; +import { TokenModel } from '../models'; import { Request } from '../request'; import { Response } from '../response'; -import { BearerTokenType } from '../token-types/bearer-token-type'; +import { BearerTokenType } from '../token-types'; import { hasOwnProperty } from '../utils/fn'; import * as is from '../validator/is'; diff --git a/lib/models/index.ts b/lib/models/index.ts new file mode 100644 index 000000000..34c69db1f --- /dev/null +++ b/lib/models/index.ts @@ -0,0 +1 @@ +export { TokenModel } from './token-model'; diff --git a/lib/models/token-model.ts b/lib/models/token-model.ts index f2b560b00..c444e9c4f 100755 --- a/lib/models/token-model.ts +++ b/lib/models/token-model.ts @@ -1,8 +1,6 @@ import { MS_IN_S } from '../constants'; -import { InvalidArgumentError } from '../errors/invalid-argument-error'; -import { Client } from '../interfaces/client.interface'; -import { Token } from '../interfaces/token.interface'; -import { User } from '../interfaces/user.interface'; +import { InvalidArgumentError } from '../errors'; +import { Client, Token, User } from '../interfaces'; import { hasOwnProperty } from '../utils/fn'; const modelAttributes = [ diff --git a/package-lock.json b/package-lock.json index 7517e7c09..0537cbbdb 100755 --- a/package-lock.json +++ b/package-lock.json @@ -70,27 +70,27 @@ } }, "@types/lodash": { - "version": "4.14.129", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.129.tgz", - "integrity": "sha512-oYaV0eSlnOacOr7i4X1FFdH8ttSlb57gu3I9MuStIv2CYkISEY84dNHYsC3bF6sNH7qYcu1BtVrCtQ8Q4KPTfQ==", + "version": "4.14.133", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.133.tgz", + "integrity": "sha512-/3JqnvPnY58GLzG3Y7fpphOhATV1DDZ/Ak3DQufjlRK5E4u+s0CfClfNFtAGBabw+jDGtRFbOZe+Z02ZMWCBNQ==", "dev": true }, "@types/mocha": { - "version": "5.2.6", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.6.tgz", - "integrity": "sha512-1axi39YdtBI7z957vdqXI4Ac25e7YihYQtJa+Clnxg1zTJEaIRbndt71O3sP4GAMgiAm0pY26/b9BrY4MR/PMw==", + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", + "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", "dev": true }, "@types/node": { - "version": "11.13.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.11.tgz", - "integrity": "sha512-blLeR+KIy26km1OU8yTLUlSyVCOvT6+wPq/77tIA+uSHHa4yYQosn+bbaJqPtWId0wjVClUtD7aXzDbZeKWqig==", + "version": "11.13.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.13.tgz", + "integrity": "sha512-GFWH7e4Q/OGLAO545bupVju+nE1YtLSwYAdLfSzAXnTPqoqKoXCOEtB7Cluvg9B/h2nGLhyzCDyCInYvrOE2nw==", "dev": true }, "@types/sinon": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-7.0.11.tgz", - "integrity": "sha512-6ee09Ugx6GyEr0opUIakmxIWFNmqYPjkqa3/BuxCBokA0klsOLPgMD5K4q40lH7/yZVuJVzOfQpd7pipwjngkQ==", + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-7.0.12.tgz", + "integrity": "sha512-fo0MWpVPSUrnZZhp9wyu+hhI3VJ9+Jhs+PWrokBTg3d2ryNPDOAWF1csIhQuYWBTn7KdZzXpRgpX2o6cwOlPWg==", "dev": true }, "@types/statuses": { @@ -1385,16 +1385,24 @@ } }, "ts-node": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.1.0.tgz", - "integrity": "sha512-34jpuOrxDuf+O6iW1JpgTRDFynUZ1iEqtYruBqh35gICNjN8x+LpVcPAcwzLPi9VU6mdA3ym+x233nZmZp445A==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.2.0.tgz", + "integrity": "sha512-m8XQwUurkbYqXrKqr3WHCW310utRNvV5OnRVeISeea7LoCWVcdfeB/Ntl8JYWFh+WRoUAdBgESrzKochQt7sMw==", "dev": true, "requires": { "arg": "^4.1.0", - "diff": "^3.1.0", + "diff": "^4.0.1", "make-error": "^1.1.1", "source-map-support": "^0.5.6", "yn": "^3.0.0" + }, + "dependencies": { + "diff": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", + "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==", + "dev": true + } } }, "tslib": { @@ -1403,9 +1411,9 @@ "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" }, "tslint": { - "version": "5.16.0", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.16.0.tgz", - "integrity": "sha512-UxG2yNxJ5pgGwmMzPMYh/CCnCnh0HfPgtlVRDs1ykZklufFBL1ZoTlWFRz2NQjcoEiDoRp+JyT0lhBbbH/obyA==", + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.17.0.tgz", + "integrity": "sha512-pflx87WfVoYepTet3xLfDOLDm9Jqi61UXIKePOuca0qoAZyrGWonDG9VTbji58Fy+8gciUn8Bt7y69+KEVjc/w==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -1414,7 +1422,7 @@ "commander": "^2.12.1", "diff": "^3.2.0", "glob": "^7.1.1", - "js-yaml": "^3.13.0", + "js-yaml": "^3.13.1", "minimatch": "^3.0.4", "mkdirp": "^0.5.1", "resolve": "^1.3.2", @@ -1448,9 +1456,9 @@ } }, "typescript": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.5.tgz", - "integrity": "sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.1.tgz", + "integrity": "sha512-64HkdiRv1yYZsSe4xC1WVgamNigVYjlssIoaH2HcZF0+ijsk5YK2g0G34w9wJkze8+5ow4STd22AynfO6ZYYLw==", "dev": true }, "validate-npm-package-license": { diff --git a/package.json b/package.json index 58333981c..f723b2067 100755 --- a/package.json +++ b/package.json @@ -52,10 +52,10 @@ }, "devDependencies": { "@types/basic-auth": "^1.1.2", - "@types/lodash": "^4.14.129", - "@types/mocha": "^5.2.6", - "@types/node": "^11.13.11", - "@types/sinon": "^7.0.11", + "@types/lodash": "^4.14.133", + "@types/mocha": "^5.2.7", + "@types/node": "^11.13.13", + "@types/sinon": "^7.0.12", "@types/statuses": "^1.5.0", "@types/type-is": "^1.6.2", "mocha": "^6.1.4", @@ -63,9 +63,9 @@ "should": "^13.2.3", "shx": "^0.3.2", "sinon": "^7.3.2", - "ts-node": "^8.1.0", - "tslint": "^5.16.0", - "typescript": "^3.4.5" + "ts-node": "^8.2.0", + "tslint": "^5.17.0", + "typescript": "^3.5.1" }, "license": "MIT", "engines": { diff --git a/test/unit/models/token-model.spec.ts b/test/unit/models/token-model.spec.ts index b2fbd7a34..b59f488c9 100755 --- a/test/unit/models/token-model.spec.ts +++ b/test/unit/models/token-model.spec.ts @@ -1,4 +1,4 @@ -import { TokenModel } from '../../../lib/models/token-model'; +import { TokenModel } from '../../../lib/models'; /** * Test `Server`. From 4600122f4863a18ac1632a799098e23ee4da3d41 Mon Sep 17 00:00:00 2001 From: Ankit Date: Thu, 11 Jul 2019 08:28:49 +0530 Subject: [PATCH 16/30] updated-dependencies --- .../authorization-code-grant-type.ts | 2 +- lib/handlers/authenticate-handler.ts | 7 +-- lib/handlers/revoke-handler.ts | 4 +- package-lock.json | 54 +++++++++---------- package.json | 16 +++--- .../refresh-token-grant-type.spec.ts | 6 +-- .../handlers/authorize-handler.spec.ts | 6 +-- .../handlers/token-handler.spec.ts | 4 +- test/integration/request.spec.ts | 3 +- test/integration/response.spec.ts | 3 +- test/tslint.json | 1 - .../handlers/authenticate-handler.spec.ts | 2 +- 12 files changed, 55 insertions(+), 53 deletions(-) diff --git a/lib/grant-types/authorization-code-grant-type.ts b/lib/grant-types/authorization-code-grant-type.ts index cecdc159f..f00c82c71 100755 --- a/lib/grant-types/authorization-code-grant-type.ts +++ b/lib/grant-types/authorization-code-grant-type.ts @@ -105,7 +105,7 @@ export class AuthorizationCodeGrantType extends AbstractGrantType { ); } - if (code.expiresAt < new Date()) { + if (code.expiresAt.getTime() < Date.now()) { throw new InvalidGrantError( 'Invalid grant: authorization code has expired', ); diff --git a/lib/handlers/authenticate-handler.ts b/lib/handlers/authenticate-handler.ts index c85c6b978..c9e61c52a 100755 --- a/lib/handlers/authenticate-handler.ts +++ b/lib/handlers/authenticate-handler.ts @@ -1,3 +1,4 @@ +import { isUndefined } from 'lodash'; import { InsufficientScopeError, InvalidArgumentError, @@ -28,13 +29,13 @@ export class AuthenticateHandler { ); } - if (options.scope && options.addAcceptedScopesHeader === undefined) { + if (options.scope && isUndefined(options.addAcceptedScopesHeader)) { throw new InvalidArgumentError( 'Missing parameter: `addAcceptedScopesHeader`', ); } - if (options.scope && options.addAuthorizedScopesHeader === undefined) { + if (options.scope && isUndefined(options.addAuthorizedScopesHeader)) { throw new InvalidArgumentError( 'Missing parameter: `addAuthorizedScopesHeader`', ); @@ -235,7 +236,7 @@ export class AuthenticateHandler { ); } - if (accessToken.accessTokenExpiresAt < new Date()) { + if (accessToken.accessTokenExpiresAt.getTime() < Date.now()) { throw new InvalidTokenError('Invalid token: access token has expired'); } diff --git a/lib/handlers/revoke-handler.ts b/lib/handlers/revoke-handler.ts index c57bac321..04a6ccde3 100644 --- a/lib/handlers/revoke-handler.ts +++ b/lib/handlers/revoke-handler.ts @@ -257,7 +257,7 @@ export class RevokeHandler { if ( refreshToken.refreshTokenExpiresAt && - refreshToken.refreshTokenExpiresAt < new Date() + refreshToken.refreshTokenExpiresAt.getTime() < Date.now() ) { throw new InvalidTokenError('Invalid token: refresh token has expired'); } @@ -300,7 +300,7 @@ export class RevokeHandler { if ( accessToken.accessTokenExpiresAt && - accessToken.accessTokenExpiresAt < new Date() + accessToken.accessTokenExpiresAt.getTime() < Date.now() ) { throw new InvalidTokenError('Invalid token: access token has expired.'); } diff --git a/package-lock.json b/package-lock.json index 0537cbbdb..5e2628c21 100755 --- a/package-lock.json +++ b/package-lock.json @@ -14,9 +14,9 @@ } }, "@babel/highlight": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", - "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", + "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", "dev": true, "requires": { "chalk": "^2.0.0", @@ -70,9 +70,9 @@ } }, "@types/lodash": { - "version": "4.14.133", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.133.tgz", - "integrity": "sha512-/3JqnvPnY58GLzG3Y7fpphOhATV1DDZ/Ak3DQufjlRK5E4u+s0CfClfNFtAGBabw+jDGtRFbOZe+Z02ZMWCBNQ==", + "version": "4.14.136", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.136.tgz", + "integrity": "sha512-0GJhzBdvsW2RUccNHOBkabI8HZVdOXmXbXhuKlDEd5Vv12P7oAVGfomGp3Ne21o5D/qu1WmthlNKFaoZJJeErA==", "dev": true }, "@types/mocha": { @@ -82,15 +82,15 @@ "dev": true }, "@types/node": { - "version": "11.13.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.13.tgz", - "integrity": "sha512-GFWH7e4Q/OGLAO545bupVju+nE1YtLSwYAdLfSzAXnTPqoqKoXCOEtB7Cluvg9B/h2nGLhyzCDyCInYvrOE2nw==", + "version": "11.13.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.17.tgz", + "integrity": "sha512-7W3kSMa8diVH6s24a8Qrmvwu+vG3ahOC/flMHFdWSdnPYoQI0yPO84h5zOWYXAha2Npn3Pw3SSuQSwBUfaniyQ==", "dev": true }, "@types/sinon": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-7.0.12.tgz", - "integrity": "sha512-fo0MWpVPSUrnZZhp9wyu+hhI3VJ9+Jhs+PWrokBTg3d2ryNPDOAWF1csIhQuYWBTn7KdZzXpRgpX2o6cwOlPWg==", + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-7.0.13.tgz", + "integrity": "sha512-d7c/C/+H/knZ3L8/cxhicHUiTDxdgap0b/aNJfsmLwFu/iOP17mdgbQsbHA3SJmrzsjD0l3UEE5SN4xxuz5ung==", "dev": true }, "@types/statuses": { @@ -686,9 +686,9 @@ } }, "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + "version": "4.17.14", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.14.tgz", + "integrity": "sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==" }, "log-symbols": { "version": "2.2.0", @@ -1385,9 +1385,9 @@ } }, "ts-node": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.2.0.tgz", - "integrity": "sha512-m8XQwUurkbYqXrKqr3WHCW310utRNvV5OnRVeISeea7LoCWVcdfeB/Ntl8JYWFh+WRoUAdBgESrzKochQt7sMw==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.3.0.tgz", + "integrity": "sha512-dyNS/RqyVTDcmNM4NIBAeDMpsAdaQ+ojdf0GOLqE6nwJOgzEkdRNzJywhDfwnuvB10oa6NLVG1rUJQCpRN7qoQ==", "dev": true, "requires": { "arg": "^4.1.0", @@ -1406,14 +1406,14 @@ } }, "tslib": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", - "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" }, "tslint": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.17.0.tgz", - "integrity": "sha512-pflx87WfVoYepTet3xLfDOLDm9Jqi61UXIKePOuca0qoAZyrGWonDG9VTbji58Fy+8gciUn8Bt7y69+KEVjc/w==", + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.18.0.tgz", + "integrity": "sha512-Q3kXkuDEijQ37nXZZLKErssQVnwCV/+23gFEMROi8IlbaBG6tXqLPQJ5Wjcyt/yHPKBC+hD5SzuGaMora+ZS6w==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -1456,9 +1456,9 @@ } }, "typescript": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.1.tgz", - "integrity": "sha512-64HkdiRv1yYZsSe4xC1WVgamNigVYjlssIoaH2HcZF0+ijsk5YK2g0G34w9wJkze8+5ow4STd22AynfO6ZYYLw==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz", + "integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==", "dev": true }, "validate-npm-package-license": { diff --git a/package.json b/package.json index f723b2067..e2935086d 100755 --- a/package.json +++ b/package.json @@ -45,17 +45,17 @@ "main": "index.js", "dependencies": { "basic-auth": "^2.0.1", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "statuses": "^1.5.0", - "tslib": "^1.9.3", + "tslib": "^1.10.0", "type-is": "^1.6.18" }, "devDependencies": { "@types/basic-auth": "^1.1.2", - "@types/lodash": "^4.14.133", + "@types/lodash": "^4.14.136", "@types/mocha": "^5.2.7", - "@types/node": "^11.13.13", - "@types/sinon": "^7.0.12", + "@types/node": "^11.13.17", + "@types/sinon": "^7.0.13", "@types/statuses": "^1.5.0", "@types/type-is": "^1.6.2", "mocha": "^6.1.4", @@ -63,9 +63,9 @@ "should": "^13.2.3", "shx": "^0.3.2", "sinon": "^7.3.2", - "ts-node": "^8.2.0", - "tslint": "^5.17.0", - "typescript": "^3.5.1" + "ts-node": "^8.3.0", + "tslint": "^5.18.0", + "typescript": "^3.5.3" }, "license": "MIT", "engines": { diff --git a/test/integration/grant-types/refresh-token-grant-type.spec.ts b/test/integration/grant-types/refresh-token-grant-type.spec.ts index fe40ddf88..60321c3b0 100755 --- a/test/integration/grant-types/refresh-token-grant-type.spec.ts +++ b/test/integration/grant-types/refresh-token-grant-type.spec.ts @@ -41,7 +41,7 @@ describe('RefreshTokenGrantType integration', () => { it('should throw an error if the model does not implement `revokeToken()`', () => { try { const model = { - async getRefreshToken() {}, + getRefreshToken() {}, }; new RefreshTokenGrantType({ accessTokenLifetime: 3600, model }); @@ -58,8 +58,8 @@ describe('RefreshTokenGrantType integration', () => { it('should throw an error if the model does not implement `saveToken()`', () => { try { const model = { - async getRefreshToken() {}, - async revokeToken() {}, + getRefreshToken() {}, + revokeToken() {}, }; new RefreshTokenGrantType({ accessTokenLifetime: 3600, model }); diff --git a/test/integration/handlers/authorize-handler.spec.ts b/test/integration/handlers/authorize-handler.spec.ts index 18a42f875..2236526a6 100755 --- a/test/integration/handlers/authorize-handler.spec.ts +++ b/test/integration/handlers/authorize-handler.spec.ts @@ -1568,9 +1568,9 @@ describe('AuthorizeHandler integration', () => { it('should return a redirect uri', () => { const error = new InvalidClientError(); const model = { - async getAccessToken() {}, - async getClient() {}, - async saveAuthorizationCode() {}, + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, }; const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, diff --git a/test/integration/handlers/token-handler.spec.ts b/test/integration/handlers/token-handler.spec.ts index b1058508b..e7c71a629 100755 --- a/test/integration/handlers/token-handler.spec.ts +++ b/test/integration/handlers/token-handler.spec.ts @@ -796,7 +796,7 @@ describe('TokenHandler integration', () => { async getClient() { return client; }, - async saveToken() {}, + saveToken() {}, }; const handler = new TokenHandler({ @@ -832,7 +832,7 @@ describe('TokenHandler integration', () => { async getClient() { return client; }, - async saveToken() {}, + saveToken() {}, }; const handler = new TokenHandler({ diff --git a/test/integration/request.spec.ts b/test/integration/request.spec.ts index c78f8e66d..cf93e854c 100755 --- a/test/integration/request.spec.ts +++ b/test/integration/request.spec.ts @@ -1,3 +1,4 @@ +import { isUndefined } from 'lodash'; import * as should from 'should'; import { InvalidArgumentError } from '../../lib/errors'; import { Request } from '../../lib/request'; @@ -95,7 +96,7 @@ describe('Request integration', () => { query: {}, }); - (request.get('content-type') === undefined).should.be.true(); + isUndefined(request.get('content-type')).should.be.true(); }); it('should return the value if the field exists', () => { diff --git a/test/integration/response.spec.ts b/test/integration/response.spec.ts index dc73475af..5e29e27dd 100755 --- a/test/integration/response.spec.ts +++ b/test/integration/response.spec.ts @@ -1,3 +1,4 @@ +import { isUndefined } from 'lodash'; import { Response } from '../../lib/response'; /** @@ -32,7 +33,7 @@ describe('Response integration', () => { it('should return `undefined` if the field does not exist', () => { const response = new Response({ body: {}, headers: {} }); - (response.get('content-type') === undefined).should.be.true(); + isUndefined(response.get('content-type')).should.be.true(); }); it('should return the value if the field exists', () => { diff --git a/test/tslint.json b/test/tslint.json index d0e928687..8c62b6de9 100755 --- a/test/tslint.json +++ b/test/tslint.json @@ -1,5 +1,4 @@ { - // "defaultSeverity": "error", "extends": ["tslint:recommended", "tslint:all", "tslint:latest"], "jsRules": { "no-unused-expression": true diff --git a/test/unit/handlers/authenticate-handler.spec.ts b/test/unit/handlers/authenticate-handler.spec.ts index 21672e4f1..7b7708df9 100755 --- a/test/unit/handlers/authenticate-handler.spec.ts +++ b/test/unit/handlers/authenticate-handler.spec.ts @@ -103,7 +103,7 @@ describe('AuthenticateHandler', () => { }); describe('validateAccessToken()', () => { - it('should fail if token has no valid `accessTokenExpiresAt` date', async () => { + it('should fail if token has no valid `accessTokenExpiresAt` date', () => { const model = { getAccessToken() {}, }; From be5d69db96d8deb02028c228dc03b657a53669e3 Mon Sep 17 00:00:00 2001 From: Ankit Date: Thu, 18 Jul 2019 13:33:40 +0530 Subject: [PATCH 17/30] fixed-linting --- index.ts | 6 +++--- lib/grant-types/refresh-token-grant-type.ts | 2 +- package-lock.json | 6 +++--- package.json | 4 ++-- scripts/build-prod.js | 1 + tslint.json | 1 - 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/index.ts b/index.ts index 94825ea61..3cea1819c 100755 --- a/index.ts +++ b/index.ts @@ -2,9 +2,9 @@ export * from './lib/errors'; export * from './lib/grant-types'; export * from './lib/handlers'; export * from './lib/interfaces'; -export * from './lib/response-types'; -export * from './lib/token-types'; export { Request } from './lib/request'; export { Response } from './lib/response'; +export * from './lib/response-types'; export { OAuth2Server } from './lib/server'; -export * from './lib/validator/is'; \ No newline at end of file +export * from './lib/token-types'; +export * from './lib/validator/is'; diff --git a/lib/grant-types/refresh-token-grant-type.ts b/lib/grant-types/refresh-token-grant-type.ts index 09d16e56e..8e7d962e0 100755 --- a/lib/grant-types/refresh-token-grant-type.ts +++ b/lib/grant-types/refresh-token-grant-type.ts @@ -103,7 +103,7 @@ export class RefreshTokenGrantType extends AbstractGrantType { if ( token.refreshTokenExpiresAt && - token.refreshTokenExpiresAt < new Date() + token.refreshTokenExpiresAt.getTime() < Date.now() ) { throw new InvalidGrantError('Invalid grant: refresh token has expired'); } diff --git a/package-lock.json b/package-lock.json index 5e2628c21..c2b8ae607 100755 --- a/package-lock.json +++ b/package-lock.json @@ -82,9 +82,9 @@ "dev": true }, "@types/node": { - "version": "11.13.17", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.17.tgz", - "integrity": "sha512-7W3kSMa8diVH6s24a8Qrmvwu+vG3ahOC/flMHFdWSdnPYoQI0yPO84h5zOWYXAha2Npn3Pw3SSuQSwBUfaniyQ==", + "version": "11.13.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.18.tgz", + "integrity": "sha512-sMHdXLccscaxI+Hlzz58yLQue3lQyXP+6aQniDRi5oju3n0123kcjhicVGF20WF7cHiwJ2vxMbXc4skOclosoA==", "dev": true }, "@types/sinon": { diff --git a/package.json b/package.json index e2935086d..80fc478ee 100755 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "email": "marco.luethy@gmail.com" } ], - "main": "index.js", + "main": "index.ts", "dependencies": { "basic-auth": "^2.0.1", "lodash": "^4.17.14", @@ -54,7 +54,7 @@ "@types/basic-auth": "^1.1.2", "@types/lodash": "^4.14.136", "@types/mocha": "^5.2.7", - "@types/node": "^11.13.17", + "@types/node": "^11.13.18", "@types/sinon": "^7.0.13", "@types/statuses": "^1.5.0", "@types/type-is": "^1.6.2", diff --git a/scripts/build-prod.js b/scripts/build-prod.js index 528b55a72..3f2771837 100644 --- a/scripts/build-prod.js +++ b/scripts/build-prod.js @@ -3,6 +3,7 @@ const packageJson = require('../package.json'); delete packageJson.scripts; delete packageJson.devDependencies; +packageJson.main = 'index.js'; fs.writeFileSync( __dirname + '/../dist/package.json', JSON.stringify(packageJson, null, 2), diff --git a/tslint.json b/tslint.json index 0091ad1da..db341dfc4 100755 --- a/tslint.json +++ b/tslint.json @@ -1,5 +1,4 @@ { - // "defaultSeverity": "error", "extends": ["tslint:recommended", "tslint:all", "tslint:latest"], "jsRules": { "no-unused-expression": true From 098ed8425324b71e4b62904c4992c1ef28093c52 Mon Sep 17 00:00:00 2001 From: Ankit Date: Tue, 23 Jul 2019 20:06:24 +0530 Subject: [PATCH 18/30] added-vsode-settings-file-gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 33b10235c..ed37ca5c4 100755 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ /.idea /.awcache /.vscode/*.code-workspace +/.vscode/settings.json # misc npm-debug.log From 737682af226b6b41eb2ae4ef8052e6c7d928f9b8 Mon Sep 17 00:00:00 2001 From: Ankit Date: Tue, 23 Jul 2019 20:22:55 +0530 Subject: [PATCH 19/30] extend-model-object-with-request-in-revoke-handler --- lib/handlers/revoke-handler.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/handlers/revoke-handler.ts b/lib/handlers/revoke-handler.ts index 04a6ccde3..0636f99fa 100644 --- a/lib/handlers/revoke-handler.ts +++ b/lib/handlers/revoke-handler.ts @@ -73,6 +73,9 @@ export class RevokeHandler { 'Invalid request: content must be application/x-www-form-urlencoded', ); } + // Extend model object with request + this.model.request = request; + try { const client = await this.getClient(request, response); From e4ebd21ddcfe629369766d6dccd457bc454d88fb Mon Sep 17 00:00:00 2001 From: Ankit Date: Tue, 10 Sep 2019 19:32:27 +0530 Subject: [PATCH 20/30] fixed-invalid_token-code --- lib/errors/invalid-token-error.ts | 2 +- lib/errors/oauth-error.ts | 2 +- lib/grant-types/abstract-grant-type.ts | 4 +- lib/models/token-model.ts | 2 +- lib/request.ts | 4 +- lib/response.ts | 4 +- lib/token-types/bearer-token-type.ts | 2 +- package-lock.json | 122 +++++++++--------- package.json | 16 +-- .../authorization-code-grant-type.spec.ts | 7 +- test/integration/request.spec.ts | 6 +- 11 files changed, 83 insertions(+), 88 deletions(-) diff --git a/lib/errors/invalid-token-error.ts b/lib/errors/invalid-token-error.ts index 655e347ce..ea22f3012 100755 --- a/lib/errors/invalid-token-error.ts +++ b/lib/errors/invalid-token-error.ts @@ -10,6 +10,6 @@ import { OAuthError } from './oauth-error'; export class InvalidTokenError extends OAuthError { constructor(message?: string | Error, properties?: any) { - super(message, { code: 400, name: 'invalid_token', ...properties }); + super(message, { code: 401, name: 'invalid_token', ...properties }); } } diff --git a/lib/errors/oauth-error.ts b/lib/errors/oauth-error.ts index 34211b23e..a9ee7c842 100755 --- a/lib/errors/oauth-error.ts +++ b/lib/errors/oauth-error.ts @@ -25,7 +25,7 @@ export class OAuthError extends Error { } this.code = this.status = this.statusCode = props.code; this.message = message; - for (const key in props) { + for (const key of Object.keys(props)) { if (key !== 'code') { this[key] = props[key]; } diff --git a/lib/grant-types/abstract-grant-type.ts b/lib/grant-types/abstract-grant-type.ts index 8839d6b88..f1260118e 100755 --- a/lib/grant-types/abstract-grant-type.ts +++ b/lib/grant-types/abstract-grant-type.ts @@ -36,7 +36,7 @@ export class AbstractGrantType { if (this.model.generateAccessToken) { const token = await this.model.generateAccessToken(client, user, scope); - return token || tokenUtil.GenerateRandomToken(); + return token ? token : tokenUtil.GenerateRandomToken(); } return tokenUtil.GenerateRandomToken(); @@ -50,7 +50,7 @@ export class AbstractGrantType { if (this.model.generateRefreshToken) { const token = await this.model.generateRefreshToken(client, user, scope); - return token || tokenUtil.GenerateRandomToken(); + return token ? token : tokenUtil.GenerateRandomToken(); } return tokenUtil.GenerateRandomToken(); diff --git a/lib/models/token-model.ts b/lib/models/token-model.ts index c444e9c4f..4472429e6 100755 --- a/lib/models/token-model.ts +++ b/lib/models/token-model.ts @@ -65,7 +65,7 @@ export class TokenModel implements Token { if (options && options.allowExtendedTokenAttributes) { this.customAttributes = {}; - for (const key in data) { + for (const key of Object.keys(data)) { if (hasOwnProperty(data, key) && modelAttributes.indexOf(key) < 0) { this.customAttributes[key] = data[key]; } diff --git a/lib/request.ts b/lib/request.ts index 6e70938ec..3ca54fd9e 100755 --- a/lib/request.ts +++ b/lib/request.ts @@ -26,14 +26,14 @@ export class Request { this.query = options.query; // Store the headers in lower case. - for (const field in options.headers) { + for (const field of Object.keys(options.headers)) { if (hasOwnProperty(options.headers, field)) { this.headers[field.toLowerCase()] = options.headers[field]; } } // Store additional properties of the request object passed in - for (const property in options) { + for (const property of Object.keys(options)) { if (hasOwnProperty(options, property) && !this[property]) { this[property] = options[property]; } diff --git a/lib/response.ts b/lib/response.ts index 2891b25f6..52052ac5c 100755 --- a/lib/response.ts +++ b/lib/response.ts @@ -10,14 +10,14 @@ export class Response { this.status = 200; // Store the headers in lower case. - for (const field in options.headers) { + for (const field of Object.keys(options.headers || {})) { if (hasOwnProperty(options.headers, field)) { this.headers[field.toLowerCase()] = options.headers[field]; } } // Store additional properties of the response object passed in - for (const property in options) { + for (const property of Object.keys(options)) { if (hasOwnProperty(options, property) && !this[property]) { this[property] = options[property]; } diff --git a/lib/token-types/bearer-token-type.ts b/lib/token-types/bearer-token-type.ts index 26967b334..dd08c4e72 100755 --- a/lib/token-types/bearer-token-type.ts +++ b/lib/token-types/bearer-token-type.ts @@ -50,7 +50,7 @@ export class BearerTokenType { object.scope = this.scope; } - for (const key in this.customAttributes) { + for (const key of Object.keys(this.customAttributes || {})) { if (hasOwnProperty(this.customAttributes, key)) { object[key] = this.customAttributes[key]; } diff --git a/package-lock.json b/package-lock.json index c2b8ae607..336e247e3 100755 --- a/package-lock.json +++ b/package-lock.json @@ -5,9 +5,9 @@ "requires": true, "dependencies": { "@babel/code-frame": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", + "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", "dev": true, "requires": { "@babel/highlight": "^7.0.0" @@ -25,9 +25,9 @@ } }, "@sinonjs/commons": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.4.0.tgz", - "integrity": "sha512-9jHK3YF/8HtJ9wCAbG+j8cD0i0+ATS9A7gXFqS36TblLPNy6rEEc+SB0imo91eCboGaBYGV/MT1/br/J+EE7Tw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.6.0.tgz", + "integrity": "sha512-w4/WHG7C4WWFyE5geCieFJF6MZkbW4VAriol5KlmQXpAQdxvV0p26sqNZOW6Qyw6Y0l9K4g+cHvvczR2sEEpqg==", "dev": true, "requires": { "type-detect": "4.0.8" @@ -44,14 +44,14 @@ } }, "@sinonjs/samsam": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.1.tgz", - "integrity": "sha512-wRSfmyd81swH0hA1bxJZJ57xr22kC07a1N4zuIL47yTS04bDk6AoCkczcqHEjcRPmJ+FruGJ9WBQiJwMtIElFw==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", + "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", "dev": true, "requires": { - "@sinonjs/commons": "^1.0.2", + "@sinonjs/commons": "^1.3.0", "array-from": "^2.1.1", - "lodash": "^4.17.11" + "lodash": "^4.17.15" } }, "@sinonjs/text-encoding": { @@ -70,9 +70,9 @@ } }, "@types/lodash": { - "version": "4.14.136", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.136.tgz", - "integrity": "sha512-0GJhzBdvsW2RUccNHOBkabI8HZVdOXmXbXhuKlDEd5Vv12P7oAVGfomGp3Ne21o5D/qu1WmthlNKFaoZJJeErA==", + "version": "4.14.138", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.138.tgz", + "integrity": "sha512-A4uJgHz4hakwNBdHNPdxOTkYmXNgmUAKLbXZ7PKGslgeV0Mb8P3BlbYfPovExek1qnod4pDfRbxuzcVs3dlFLg==", "dev": true }, "@types/mocha": { @@ -82,9 +82,9 @@ "dev": true }, "@types/node": { - "version": "11.13.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.18.tgz", - "integrity": "sha512-sMHdXLccscaxI+Hlzz58yLQue3lQyXP+6aQniDRi5oju3n0123kcjhicVGF20WF7cHiwJ2vxMbXc4skOclosoA==", + "version": "11.13.20", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.20.tgz", + "integrity": "sha512-JE0UpLWZTV1sGcaj0hN+Q0760OEjpgyFJ06DOMVW6qKBducKdJQaIw0TGL6ccj7VXRduIOHLWQi+tHwulZJHVQ==", "dev": true }, "@types/sinon": { @@ -100,9 +100,9 @@ "dev": true }, "@types/type-is": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/@types/type-is/-/type-is-1.6.2.tgz", - "integrity": "sha512-q8d51ZdF/D8xebrtNDsZH+4XBUFdz8xEgWhE4U4F4WWmcBZ8+i/r/qs9DmjAprYh5qQTYlY4BxaVKDrWIwNQ9w==", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@types/type-is/-/type-is-1.6.3.tgz", + "integrity": "sha512-PNs5wHaNcBgCQG5nAeeZ7OvosrEsI9O4W2jAOO9BCCg4ux9ZZvH2+0iSCOIDBiKuQsiNS8CBlmfX9f5YBQ22cA==", "dev": true, "requires": { "@types/node": "*" @@ -402,9 +402,9 @@ "dev": true }, "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, "execa": { @@ -686,9 +686,9 @@ } }, "lodash": { - "version": "4.17.14", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.14.tgz", - "integrity": "sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==" + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, "log-symbols": { "version": "2.2.0", @@ -700,9 +700,9 @@ } }, "lolex": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.0.1.tgz", - "integrity": "sha512-UHuOBZ5jjsKuzbB/gRNNW8Vg8f00Emgskdq2kvZxgBJCS0aqquAuXai/SkWORlKeZEiNQWZjFZOqIUcH9LqKCw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.2.0.tgz", + "integrity": "sha512-gKO5uExCXvSm6zbF562EvM+rd1kQDnB9AZBbiQVzf1ZmdDpxUSvpnAaVOP83N/31mRK8Ml8/VE8DMvsAZQ+7wg==", "dev": true }, "make-error": { @@ -786,9 +786,9 @@ } }, "mocha": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.1.4.tgz", - "integrity": "sha512-PN8CIy4RXsIoxoFJzS4QNnCH4psUCPWc4/rPrst/ecSJJbLBkubMiyGCP2Kj/9YnWbotFqAoeXyXMucj7gwCFg==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.0.tgz", + "integrity": "sha512-qwfFgY+7EKAAUAdv7VYMZQknI7YJSGesxHyhn6qD52DV8UcSZs5XwCifcZGMVIE4a5fbmhvbotxC0DLQ0oKohQ==", "dev": true, "requires": { "ansi-colors": "3.2.3", @@ -829,24 +829,16 @@ "dev": true }, "nise": { - "version": "1.4.10", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.4.10.tgz", - "integrity": "sha512-sa0RRbj53dovjc7wombHmVli9ZihXbXCQ2uH3TNm03DyvOSIQbxg+pbqDKrk2oxMK1rtLGVlKxcB9rrc6X5YjA==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.2.tgz", + "integrity": "sha512-/6RhOUlicRCbE9s+94qCUsyE+pKlVJ5AhIv+jEE7ESKwnbXqulKZ1FYU+XAtHHWE9TinYvAxDUJAb912PwPoWA==", "dev": true, "requires": { - "@sinonjs/formatio": "^3.1.0", + "@sinonjs/formatio": "^3.2.1", "@sinonjs/text-encoding": "^0.7.1", "just-extend": "^4.0.2", - "lolex": "^2.3.2", + "lolex": "^4.1.0", "path-to-regexp": "^1.7.0" - }, - "dependencies": { - "lolex": { - "version": "2.7.5", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.5.tgz", - "integrity": "sha512-l9x0+1offnKKIzYVjyXU2SiwhXDLekRzKyhnbyldPHvC7BvLPVpdNUNR2KeMAiCN2D/kLNttZgQD5WjSxuBx3Q==", - "dev": true - } } }, "node-environment-flags": { @@ -970,9 +962,9 @@ "dev": true }, "p-limit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", - "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", + "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -1243,17 +1235,17 @@ "dev": true }, "sinon": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.3.2.tgz", - "integrity": "sha512-thErC1z64BeyGiPvF8aoSg0LEnptSaWE7YhdWWbWXgelOyThent7uKOnnEh9zBxDbKixtr5dEko+ws1sZMuFMA==", + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.4.2.tgz", + "integrity": "sha512-pY5RY99DKelU3pjNxcWo6XqeB1S118GBcVIIdDi6V+h6hevn1izcg2xv1hTHW/sViRXU7sUOxt4wTUJ3gsW2CQ==", "dev": true, "requires": { "@sinonjs/commons": "^1.4.0", "@sinonjs/formatio": "^3.2.1", - "@sinonjs/samsam": "^3.3.1", + "@sinonjs/samsam": "^3.3.3", "diff": "^3.5.0", - "lolex": "^4.0.1", - "nise": "^1.4.10", + "lolex": "^4.2.0", + "nise": "^1.5.2", "supports-color": "^5.5.0" }, "dependencies": { @@ -1411,16 +1403,16 @@ "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" }, "tslint": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.18.0.tgz", - "integrity": "sha512-Q3kXkuDEijQ37nXZZLKErssQVnwCV/+23gFEMROi8IlbaBG6tXqLPQJ5Wjcyt/yHPKBC+hD5SzuGaMora+ZS6w==", + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.0.tgz", + "integrity": "sha512-2vqIvkMHbnx8acMogAERQ/IuINOq6DFqgF8/VDvhEkBqQh/x6SP0Y+OHnKth9/ZcHQSroOZwUQSN18v8KKF0/g==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "builtin-modules": "^1.1.1", "chalk": "^2.3.0", "commander": "^2.12.1", - "diff": "^3.2.0", + "diff": "^4.0.1", "glob": "^7.1.1", "js-yaml": "^3.13.1", "minimatch": "^3.0.4", @@ -1429,6 +1421,14 @@ "semver": "^5.3.0", "tslib": "^1.8.0", "tsutils": "^2.29.0" + }, + "dependencies": { + "diff": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", + "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==", + "dev": true + } } }, "tsutils": { @@ -1456,9 +1456,9 @@ } }, "typescript": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz", - "integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.2.tgz", + "integrity": "sha512-lmQ4L+J6mnu3xweP8+rOrUwzmN+MRAj7TgtJtDaXE5PMyX2kCrklhg3rvOsOIfNeAWMQWO2F1GPc1kMD2vLAfw==", "dev": true }, "validate-npm-package-license": { diff --git a/package.json b/package.json index 80fc478ee..2c8bfea0b 100755 --- a/package.json +++ b/package.json @@ -45,27 +45,27 @@ "main": "index.ts", "dependencies": { "basic-auth": "^2.0.1", - "lodash": "^4.17.14", + "lodash": "^4.17.15", "statuses": "^1.5.0", "tslib": "^1.10.0", "type-is": "^1.6.18" }, "devDependencies": { "@types/basic-auth": "^1.1.2", - "@types/lodash": "^4.14.136", + "@types/lodash": "^4.14.138", "@types/mocha": "^5.2.7", - "@types/node": "^11.13.18", + "@types/node": "^11.13.20", "@types/sinon": "^7.0.13", "@types/statuses": "^1.5.0", - "@types/type-is": "^1.6.2", - "mocha": "^6.1.4", + "@types/type-is": "^1.6.3", + "mocha": "^6.2.0", "npm-run-all": "^4.1.5", "should": "^13.2.3", "shx": "^0.3.2", - "sinon": "^7.3.2", + "sinon": "^7.4.2", "ts-node": "^8.3.0", - "tslint": "^5.18.0", - "typescript": "^3.5.3" + "tslint": "^5.20.0", + "typescript": "^3.6.2" }, "license": "MIT", "engines": { diff --git a/test/integration/grant-types/authorization-code-grant-type.spec.ts b/test/integration/grant-types/authorization-code-grant-type.spec.ts index 88c70fd10..05869e4bc 100755 --- a/test/integration/grant-types/authorization-code-grant-type.spec.ts +++ b/test/integration/grant-types/authorization-code-grant-type.spec.ts @@ -1,10 +1,5 @@ import * as should from 'should'; -import { - InvalidArgumentError, - InvalidGrantError, - InvalidRequestError, - ServerError, -} from '../../../lib/errors'; +import { InvalidArgumentError, InvalidGrantError, InvalidRequestError, ServerError } from '../../../lib/errors'; import { AuthorizationCodeGrantType } from '../../../lib/grant-types'; import { Request } from '../../../lib/request'; diff --git a/test/integration/request.spec.ts b/test/integration/request.spec.ts index cf93e854c..10273d0f0 100755 --- a/test/integration/request.spec.ts +++ b/test/integration/request.spec.ts @@ -11,7 +11,7 @@ describe('Request integration', () => { describe('constructor()', () => { it('should throw an error if `headers` is missing', () => { try { - new Request({ body: {} }); + new Request({ body: {} } as any); should.fail('should.fail', ''); } catch (e) { @@ -22,7 +22,7 @@ describe('Request integration', () => { it('should throw an error if `method` is missing', () => { try { - new Request({ body: {}, headers: {} }); + new Request({ body: {}, headers: {} } as any); should.fail('should.fail', ''); } catch (e) { @@ -33,7 +33,7 @@ describe('Request integration', () => { it('should throw an error if `query` is missing', () => { try { - new Request({ body: {}, headers: {}, method: {} }); + new Request({ body: {}, headers: {}, method: {} } as any); should.fail('should.fail', ''); } catch (e) { From 3a6abf699ae5c353b76d2431730db0b4a6dc09c1 Mon Sep 17 00:00:00 2001 From: Ankit Date: Tue, 10 Sep 2019 21:14:38 +0530 Subject: [PATCH 21/30] fixed-magic-time-constants --- lib/constants/common.ts | 13 ++++++++++++- lib/grant-types/abstract-grant-type.ts | 10 +++++++--- lib/models/token-model.ts | 5 +++-- lib/response-types/code-response-type.ts | 6 ++++-- lib/server.ts | 9 +++++---- 5 files changed, 31 insertions(+), 12 deletions(-) diff --git a/lib/constants/common.ts b/lib/constants/common.ts index 934dcebdb..d8caa68f2 100644 --- a/lib/constants/common.ts +++ b/lib/constants/common.ts @@ -1 +1,12 @@ -export const MS_IN_S = 1_000; +export const MILLISECONDS_PER_SECOND = 1_000; +export const SECONDS_PER_MINUTE = 60; +export const MINUTES_PER_HOUR = 60; +export const HOURS_PER_DAY = 24; +export const DAYS_PER_WEEK = 7; +export const MONTHS_PER_YEAR = 12; + +export const SECOND = MILLISECONDS_PER_SECOND; +export const MINUTE = SECONDS_PER_MINUTE * SECOND; +export const HOUR = MINUTES_PER_HOUR * MINUTE; +export const DAY = HOURS_PER_DAY * HOUR; +export const WEEK = DAYS_PER_WEEK * DAY; diff --git a/lib/grant-types/abstract-grant-type.ts b/lib/grant-types/abstract-grant-type.ts index f1260118e..8d9adea38 100755 --- a/lib/grant-types/abstract-grant-type.ts +++ b/lib/grant-types/abstract-grant-type.ts @@ -1,4 +1,4 @@ -import { MS_IN_S } from '../constants'; +import { MILLISECONDS_PER_SECOND } from '../constants'; import { InvalidArgumentError, InvalidScopeError } from '../errors'; import { Client, Model, User } from '../interfaces'; import { Request } from '../request'; @@ -61,7 +61,9 @@ export class AbstractGrantType { */ getAccessTokenExpiresAt() { - return new Date(Date.now() + this.accessTokenLifetime * MS_IN_S); + return new Date( + Date.now() + this.accessTokenLifetime * MILLISECONDS_PER_SECOND, + ); } /** @@ -69,7 +71,9 @@ export class AbstractGrantType { */ getRefreshTokenExpiresAt() { - return new Date(Date.now() + this.refreshTokenLifetime * MS_IN_S); + return new Date( + Date.now() + this.refreshTokenLifetime * MILLISECONDS_PER_SECOND, + ); } /** diff --git a/lib/models/token-model.ts b/lib/models/token-model.ts index 4472429e6..e5104901f 100755 --- a/lib/models/token-model.ts +++ b/lib/models/token-model.ts @@ -1,4 +1,4 @@ -import { MS_IN_S } from '../constants'; +import { MILLISECONDS_PER_SECOND } from '../constants'; import { InvalidArgumentError } from '../errors'; import { Client, Token, User } from '../interfaces'; import { hasOwnProperty } from '../utils/fn'; @@ -74,7 +74,8 @@ export class TokenModel implements Token { if (this.accessTokenExpiresAt) { this.accessTokenLifetime = Math.floor( - (this.accessTokenExpiresAt.getTime() - new Date().getTime()) / MS_IN_S, + (this.accessTokenExpiresAt.getTime() - new Date().getTime()) / + MILLISECONDS_PER_SECOND, ); } } diff --git a/lib/response-types/code-response-type.ts b/lib/response-types/code-response-type.ts index 5e93c5aee..0d1b93e31 100755 --- a/lib/response-types/code-response-type.ts +++ b/lib/response-types/code-response-type.ts @@ -1,4 +1,4 @@ -import { MS_IN_S } from '../constants'; +import { MILLISECONDS_PER_SECOND } from '../constants'; import { InvalidArgumentError } from '../errors'; import { AuthorizationCode, Client, Model, User } from '../interfaces'; import { Request } from '../request'; @@ -83,7 +83,9 @@ export class CodeResponseType { getAuthorizationCodeExpiresAt(client: Client) { const authorizationCodeLifetime = this.getAuthorizationCodeLifetime(client); - return new Date(Date.now() + authorizationCodeLifetime * MS_IN_S); + return new Date( + Date.now() + authorizationCodeLifetime * MILLISECONDS_PER_SECOND, + ); } /** diff --git a/lib/server.ts b/lib/server.ts index be188686a..3e81e441d 100755 --- a/lib/server.ts +++ b/lib/server.ts @@ -1,3 +1,4 @@ +import { HOUR, MINUTE, SECOND, WEEK } from './constants'; import { InvalidArgumentError } from './errors'; import { AuthenticateHandler, @@ -61,8 +62,8 @@ export class OAuth2Server { async authorize(request: Request, response: Response, options?: any) { const opts = { allowEmptyState: false, - accessTokenLifetime: 60 * 60, - authorizationCodeLifetime: 5 * 60, + accessTokenLifetime: HOUR / SECOND, + authorizationCodeLifetime: (MINUTE * 5) / SECOND, ...this.options, ...options, }; @@ -76,8 +77,8 @@ export class OAuth2Server { async token(request: Request, response: Response, options?: any) { const opts = { - accessTokenLifetime: 60 * 60, // 1 hour. - refreshTokenLifetime: 60 * 60 * 24 * 14, // 2 weeks. + accessTokenLifetime: HOUR / SECOND, // 1 hour in seconds. + refreshTokenLifetime: (WEEK * 2) / SECOND, // 2 weeks. allowExtendedTokenAttributes: false, requireClientAuthentication: {}, ...this.options, From c2e6a22d2a596558382bb0bbb80936af4064bf24 Mon Sep 17 00:00:00 2001 From: Ankit Date: Wed, 11 Sep 2019 15:10:23 +0530 Subject: [PATCH 22/30] request-method-to-upper-case-and-updated-test --- lib/handlers/token-handler.ts | 9 +-- lib/request.ts | 18 ++++- .../grant-types/abstract-grant-type.spec.ts | 6 +- .../authorization-code-grant-type.spec.ts | 49 +++++++------ .../client-credentials-grant-type.spec.ts | 18 ++--- .../grant-types/implicit-grant-type.spec.ts | 10 +-- .../grant-types/password-grant-type.spec.ts | 26 +++---- .../refresh-token-grant-type.spec.ts | 36 +++++----- .../handlers/authenticate-handler.spec.ts | 20 +++--- .../handlers/authorize-handler.spec.ts | 70 +++++++++---------- .../handlers/revoke-handler.spec.ts | 36 +++++----- .../handlers/token-handler.spec.ts | 56 +++++++-------- test/integration/request.spec.ts | 24 +++---- test/integration/server.spec.ts | 12 ++-- .../authorization-code-grant-type.spec.ts | 2 +- .../grant-types/password-grant-type.spec.ts | 2 +- .../refresh-token-grant-type.spec.ts | 4 +- .../handlers/authenticate-handler.spec.ts | 6 +- test/unit/handlers/authorize-handler.spec.ts | 4 +- test/unit/handlers/revoke-handlers.spec.ts | 4 +- test/unit/handlers/token-handler.spec.ts | 2 +- 21 files changed, 216 insertions(+), 198 deletions(-) diff --git a/lib/handlers/token-handler.ts b/lib/handlers/token-handler.ts index 286a1b41a..18db66155 100755 --- a/lib/handlers/token-handler.ts +++ b/lib/handlers/token-handler.ts @@ -209,10 +209,11 @@ export class TokenHandler { }; } - if (!this.isClientAuthenticationRequired(grantType)) { - if (request.body.client_id) { - return { clientId: request.body.client_id }; - } + if ( + !this.isClientAuthenticationRequired(grantType) && + request.body.client_id + ) { + return { clientId: request.body.client_id }; } throw new InvalidClientError( diff --git a/lib/request.ts b/lib/request.ts index 3ca54fd9e..5bfdfe467 100755 --- a/lib/request.ts +++ b/lib/request.ts @@ -5,9 +5,17 @@ import { hasOwnProperty } from './utils/fn'; export class Request { body: any; headers: any; - method: any; + method: string; query: any; - constructor(options: any = {}) { + constructor( + options: { + body: any; + headers: any; + method: string; + query: any; + [key: string]: any; + } = {} as any, + ) { if (!options.headers) { throw new InvalidArgumentError('Missing parameter: `headers`'); } @@ -16,13 +24,17 @@ export class Request { throw new InvalidArgumentError('Missing parameter: `method`'); } + if (typeof options.method !== 'string') { + throw new InvalidArgumentError('Invalid parameter: `method`'); + } + if (!options.query) { throw new InvalidArgumentError('Missing parameter: `query`'); } this.body = options.body || {}; this.headers = {}; - this.method = options.method; + this.method = options.method.toUpperCase(); this.query = options.query; // Store the headers in lower case. diff --git a/test/integration/grant-types/abstract-grant-type.spec.ts b/test/integration/grant-types/abstract-grant-type.spec.ts index 23a8c37cd..2bde67443 100755 --- a/test/integration/grant-types/abstract-grant-type.spec.ts +++ b/test/integration/grant-types/abstract-grant-type.spec.ts @@ -183,7 +183,7 @@ describe('AbstractGrantType integration', () => { const request = new Request({ body: { scope: 'øå€£‰' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -205,7 +205,7 @@ describe('AbstractGrantType integration', () => { const request = new Request({ body: {}, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -221,7 +221,7 @@ describe('AbstractGrantType integration', () => { const request = new Request({ body: { scope: 'foo' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); diff --git a/test/integration/grant-types/authorization-code-grant-type.spec.ts b/test/integration/grant-types/authorization-code-grant-type.spec.ts index 05869e4bc..f052a7b34 100755 --- a/test/integration/grant-types/authorization-code-grant-type.spec.ts +++ b/test/integration/grant-types/authorization-code-grant-type.spec.ts @@ -1,5 +1,10 @@ import * as should from 'should'; -import { InvalidArgumentError, InvalidGrantError, InvalidRequestError, ServerError } from '../../../lib/errors'; +import { + InvalidArgumentError, + InvalidGrantError, + InvalidRequestError, + ServerError, +} from '../../../lib/errors'; import { AuthorizationCodeGrantType } from '../../../lib/grant-types'; import { Request } from '../../../lib/request'; @@ -113,7 +118,7 @@ describe('AuthorizationCodeGrantType integration', () => { const request = new Request({ body: { code: 12345 }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -149,7 +154,7 @@ describe('AuthorizationCodeGrantType integration', () => { const request = new Request({ body: { code: 12345 }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -190,7 +195,7 @@ describe('AuthorizationCodeGrantType integration', () => { const request = new Request({ body: { code: 12345 }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); try { @@ -224,7 +229,7 @@ describe('AuthorizationCodeGrantType integration', () => { const request = new Request({ body: { code: 12345 }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -254,7 +259,7 @@ describe('AuthorizationCodeGrantType integration', () => { const request = new Request({ body: { code: 12345 }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -291,7 +296,7 @@ describe('AuthorizationCodeGrantType integration', () => { // const request = new Request({ // body: { code: 12345 }, // headers: {}, - // method: {}, + // method: "ANY", // query: {}, // }); @@ -314,7 +319,7 @@ describe('AuthorizationCodeGrantType integration', () => { const request = new Request({ body: {}, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -342,7 +347,7 @@ describe('AuthorizationCodeGrantType integration', () => { const request = new Request({ body: { code: 'øå€£‰' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -370,7 +375,7 @@ describe('AuthorizationCodeGrantType integration', () => { const request = new Request({ body: { code: 12345 }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -403,7 +408,7 @@ describe('AuthorizationCodeGrantType integration', () => { const request = new Request({ body: { code: 12345 }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -436,7 +441,7 @@ describe('AuthorizationCodeGrantType integration', () => { const request = new Request({ body: { code: 12345 }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -473,7 +478,7 @@ describe('AuthorizationCodeGrantType integration', () => { const request = new Request({ body: { code: 12345 }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -511,7 +516,7 @@ describe('AuthorizationCodeGrantType integration', () => { const request = new Request({ body: { code: 12345 }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -550,7 +555,7 @@ describe('AuthorizationCodeGrantType integration', () => { const request = new Request({ body: { code: 12345 }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -590,7 +595,7 @@ describe('AuthorizationCodeGrantType integration', () => { const request = new Request({ body: { code: 12345 }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -629,7 +634,7 @@ describe('AuthorizationCodeGrantType integration', () => { const request = new Request({ body: { code: 12345 }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -665,7 +670,7 @@ describe('AuthorizationCodeGrantType integration', () => { const request = new Request({ body: { code: 12345 }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -696,7 +701,7 @@ describe('AuthorizationCodeGrantType integration', () => { const request = new Request({ body: { code: 12345 }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -727,7 +732,7 @@ describe('AuthorizationCodeGrantType integration', () => { // const request = new Request({ // body: { code: 12345 }, // headers: {}, - // method: {}, + // method: "ANY", // query: {}, // }); @@ -760,7 +765,7 @@ describe('AuthorizationCodeGrantType integration', () => { const request = new Request({ body: { code: 12345 }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -798,7 +803,7 @@ describe('AuthorizationCodeGrantType integration', () => { const request = new Request({ body: { code: 12345, redirect_uri: 'http://bar.foo' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); diff --git a/test/integration/grant-types/client-credentials-grant-type.spec.ts b/test/integration/grant-types/client-credentials-grant-type.spec.ts index 305707f0d..d9f1e3a9a 100755 --- a/test/integration/grant-types/client-credentials-grant-type.spec.ts +++ b/test/integration/grant-types/client-credentials-grant-type.spec.ts @@ -87,7 +87,7 @@ describe('ClientCredentialsGrantType integration', () => { const request = new Request({ body: {}, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -121,7 +121,7 @@ describe('ClientCredentialsGrantType integration', () => { const request = new Request({ body: {}, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -152,7 +152,7 @@ describe('ClientCredentialsGrantType integration', () => { const request = new Request({ body: {}, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -176,7 +176,7 @@ describe('ClientCredentialsGrantType integration', () => { const request = new Request({ body: {}, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -197,7 +197,7 @@ describe('ClientCredentialsGrantType integration', () => { const request = new Request({ body: {}, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -227,7 +227,7 @@ describe('ClientCredentialsGrantType integration', () => { const request = new Request({ body: {}, headers: {}, - method: {}, + method: 'ANY', query: {}, }); try { @@ -253,7 +253,7 @@ describe('ClientCredentialsGrantType integration', () => { const request = new Request({ body: {}, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -275,7 +275,7 @@ describe('ClientCredentialsGrantType integration', () => { const request = new Request({ body: {}, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -297,7 +297,7 @@ describe('ClientCredentialsGrantType integration', () => { const request = new Request({ body: {}, headers: {}, - method: {}, + method: 'ANY', query: {}, }); diff --git a/test/integration/grant-types/implicit-grant-type.spec.ts b/test/integration/grant-types/implicit-grant-type.spec.ts index e65758256..0af699fb1 100644 --- a/test/integration/grant-types/implicit-grant-type.spec.ts +++ b/test/integration/grant-types/implicit-grant-type.spec.ts @@ -83,7 +83,7 @@ describe('ImplicitGrantType integration', () => { const request = new Request({ body: { code: 12345 }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -114,7 +114,7 @@ describe('ImplicitGrantType integration', () => { const request = new Request({ body: { code: 12345 }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -139,7 +139,7 @@ describe('ImplicitGrantType integration', () => { const request = new Request({ body: { code: 12345 }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -159,7 +159,7 @@ describe('ImplicitGrantType integration', () => { const request = new Request({ body: { code: 12345 }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -181,7 +181,7 @@ describe('ImplicitGrantType integration', () => { // const request = new Request({ // body: { code: 12345 }, // headers: {}, - // method: {}, + // method: 'ANY', // query: {}, // }); diff --git a/test/integration/grant-types/password-grant-type.spec.ts b/test/integration/grant-types/password-grant-type.spec.ts index 5cb593e1e..a02b37729 100755 --- a/test/integration/grant-types/password-grant-type.spec.ts +++ b/test/integration/grant-types/password-grant-type.spec.ts @@ -117,7 +117,7 @@ describe('PasswordGrantType integration', () => { const request = new Request({ body: { username: 'foo', password: 'bar', scope: 'baz' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -149,7 +149,7 @@ describe('PasswordGrantType integration', () => { const request = new Request({ body: { username: 'foo', password: 'bar' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -174,7 +174,7 @@ describe('PasswordGrantType integration', () => { const request = new Request({ body: { username: 'foo', password: 'bar' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -199,7 +199,7 @@ describe('PasswordGrantType integration', () => { const request = new Request({ body: { username: 'foo', password: 'bar' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -220,7 +220,7 @@ describe('PasswordGrantType integration', () => { const request = new Request({ body: {}, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -246,7 +246,7 @@ describe('PasswordGrantType integration', () => { const request = new Request({ body: { username: 'foo' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -272,7 +272,7 @@ describe('PasswordGrantType integration', () => { const request = new Request({ body: { username: '\r\n', password: 'foobar' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -298,7 +298,7 @@ describe('PasswordGrantType integration', () => { const request = new Request({ body: { username: 'foobar', password: '\r\n' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -324,7 +324,7 @@ describe('PasswordGrantType integration', () => { const request = new Request({ body: { username: 'foo', password: 'bar' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); try { @@ -351,7 +351,7 @@ describe('PasswordGrantType integration', () => { const request = new Request({ body: { username: 'foo', password: 'bar' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); try { @@ -377,7 +377,7 @@ describe('PasswordGrantType integration', () => { const request = new Request({ body: { username: 'foo', password: 'bar' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -399,7 +399,7 @@ describe('PasswordGrantType integration', () => { const request = new Request({ body: { username: 'foo', password: 'bar' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -421,7 +421,7 @@ describe('PasswordGrantType integration', () => { const request = new Request({ body: { username: 'foo', password: 'bar' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); diff --git a/test/integration/grant-types/refresh-token-grant-type.spec.ts b/test/integration/grant-types/refresh-token-grant-type.spec.ts index 60321c3b0..c37ecb9e0 100755 --- a/test/integration/grant-types/refresh-token-grant-type.spec.ts +++ b/test/integration/grant-types/refresh-token-grant-type.spec.ts @@ -109,7 +109,7 @@ describe('RefreshTokenGrantType integration', () => { const request = new Request({ body: {}, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -149,7 +149,7 @@ describe('RefreshTokenGrantType integration', () => { const request = new Request({ body: { refresh_token: 'foobar' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -192,7 +192,7 @@ describe('RefreshTokenGrantType integration', () => { const request = new Request({ body: { refresh_token: 'foobar' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -224,7 +224,7 @@ describe('RefreshTokenGrantType integration', () => { const request = new Request({ body: { refresh_token: 'foobar' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -256,7 +256,7 @@ describe('RefreshTokenGrantType integration', () => { const request = new Request({ body: { refresh_token: 'foobar' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -279,7 +279,7 @@ describe('RefreshTokenGrantType integration', () => { const request = new Request({ body: {}, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -309,7 +309,7 @@ describe('RefreshTokenGrantType integration', () => { const request = new Request({ body: { refresh_token: '12345' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -340,7 +340,7 @@ describe('RefreshTokenGrantType integration', () => { const request = new Request({ body: { refresh_token: 12345 }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -373,7 +373,7 @@ describe('RefreshTokenGrantType integration', () => { const request = new Request({ body: { refresh_token: 12345 }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -406,7 +406,7 @@ describe('RefreshTokenGrantType integration', () => { const request = new Request({ body: { refresh_token: 12345 }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -437,7 +437,7 @@ describe('RefreshTokenGrantType integration', () => { const request = new Request({ body: { refresh_token: 'øå€£‰' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -467,7 +467,7 @@ describe('RefreshTokenGrantType integration', () => { const request = new Request({ body: { refresh_token: 12345 }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -504,7 +504,7 @@ describe('RefreshTokenGrantType integration', () => { const request = new Request({ body: { refresh_token: 12345 }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -540,7 +540,7 @@ describe('RefreshTokenGrantType integration', () => { const request = new Request({ body: { refresh_token: 12345 }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -574,7 +574,7 @@ describe('RefreshTokenGrantType integration', () => { const request = new Request({ body: { refresh_token: 'foobar' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -605,7 +605,7 @@ describe('RefreshTokenGrantType integration', () => { const request = new Request({ body: { refresh_token: 'foobar' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -631,7 +631,7 @@ describe('RefreshTokenGrantType integration', () => { const request = new Request({ body: { refresh_token: 'foobar' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -657,7 +657,7 @@ describe('RefreshTokenGrantType integration', () => { // const request = new Request({ // body: { refresh_token: 'foobar' }, // headers: {}, - // method: {}, + // method: 'ANY', // query: {}, // }); diff --git a/test/integration/handlers/authenticate-handler.spec.ts b/test/integration/handlers/authenticate-handler.spec.ts index 1154e9ab8..9200e9d01 100755 --- a/test/integration/handlers/authenticate-handler.spec.ts +++ b/test/integration/handlers/authenticate-handler.spec.ts @@ -142,7 +142,7 @@ describe('AuthenticateHandler integration', () => { const request = new Request({ body: {}, headers: { Authorization: 'Bearer foo' }, - method: {}, + method: 'ANY', query: {}, }); const response = new Response({ body: {}, headers: {} }); @@ -169,7 +169,7 @@ describe('AuthenticateHandler integration', () => { const request = new Request({ body: {}, headers: { Authorization: 'Bearer foo' }, - method: {}, + method: 'ANY', query: {}, }); const response = new Response({ body: {}, headers: {} }); @@ -195,7 +195,7 @@ describe('AuthenticateHandler integration', () => { const request = new Request({ body: {}, headers: { Authorization: 'Bearer foo' }, - method: {}, + method: 'ANY', query: {}, }); const response = new Response({ body: {}, headers: {} }); @@ -233,7 +233,7 @@ describe('AuthenticateHandler integration', () => { const request = new Request({ body: {}, headers: { Authorization: 'Bearer foo' }, - method: {}, + method: 'ANY', query: {}, }); const response = new Response({ body: {}, headers: {} }); @@ -257,7 +257,7 @@ describe('AuthenticateHandler integration', () => { const request = new Request({ body: {}, headers: { Authorization: 'Bearer foo' }, - method: {}, + method: 'ANY', query: { access_token: 'foo' }, }); @@ -280,7 +280,7 @@ describe('AuthenticateHandler integration', () => { const request = new Request({ body: {}, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -305,7 +305,7 @@ describe('AuthenticateHandler integration', () => { headers: { Authorization: 'foobar', }, - method: {}, + method: 'ANY', query: {}, }); @@ -330,7 +330,7 @@ describe('AuthenticateHandler integration', () => { headers: { Authorization: 'Bearer foo', }, - method: {}, + method: 'ANY', query: {}, }); @@ -398,7 +398,7 @@ describe('AuthenticateHandler integration', () => { const request = new Request({ body: { access_token: 'foo' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -424,7 +424,7 @@ describe('AuthenticateHandler integration', () => { 'content-type': 'application/x-www-form-urlencoded', 'transfer-encoding': 'chunked', }, - method: {}, + method: 'ANY', query: {}, }); diff --git a/test/integration/handlers/authorize-handler.spec.ts b/test/integration/handlers/authorize-handler.spec.ts index 2236526a6..81eb04700 100755 --- a/test/integration/handlers/authorize-handler.spec.ts +++ b/test/integration/handlers/authorize-handler.spec.ts @@ -173,7 +173,7 @@ describe('AuthorizeHandler integration', () => { const request = new Request({ body: {}, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -202,7 +202,7 @@ describe('AuthorizeHandler integration', () => { const request = new Request({ body: {}, headers: {}, - method: {}, + method: 'ANY', query: { allowed: 'false' }, }); const response = new Response({ body: {}, headers: {} }); @@ -250,7 +250,7 @@ describe('AuthorizeHandler integration', () => { headers: { Authorization: 'Bearer foo', }, - method: {}, + method: 'ANY', query: { state: 'foobar', }, @@ -301,7 +301,7 @@ describe('AuthorizeHandler integration', () => { headers: { Authorization: 'Bearer foo', }, - method: {}, + method: 'ANY', query: { state: 'foobar', }, @@ -353,7 +353,7 @@ describe('AuthorizeHandler integration', () => { headers: { Authorization: 'Bearer foo', }, - method: {}, + method: 'ANY', query: { state: 'foobar', }, @@ -402,7 +402,7 @@ describe('AuthorizeHandler integration', () => { headers: { Authorization: 'Bearer foo', }, - method: {}, + method: 'ANY', query: { scope: [], state: 'foobar', @@ -454,7 +454,7 @@ describe('AuthorizeHandler integration', () => { headers: { Authorization: 'Bearer foo', }, - method: {}, + method: 'ANY', query: {}, }); const response = new Response({ body: {}, headers: {} }); @@ -503,7 +503,7 @@ describe('AuthorizeHandler integration', () => { headers: { Authorization: 'Bearer foo', }, - method: {}, + method: 'ANY', query: { state: 'foobar', }, @@ -553,7 +553,7 @@ describe('AuthorizeHandler integration', () => { headers: { Authorization: 'Bearer foo', }, - method: {}, + method: 'ANY', query: { state: 'foobar', }, @@ -605,7 +605,7 @@ describe('AuthorizeHandler integration', () => { headers: { Authorization: 'Bearer foo', }, - method: {}, + method: 'ANY', query: { state: 'foobar', }, @@ -721,7 +721,7 @@ describe('AuthorizeHandler integration', () => { const request = new Request({ body: { response_type: 'code' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -748,7 +748,7 @@ describe('AuthorizeHandler integration', () => { const request = new Request({ body: { client_id: 'øå€£‰', response_type: 'code' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -779,7 +779,7 @@ describe('AuthorizeHandler integration', () => { redirect_uri: 'foobar', }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -808,7 +808,7 @@ describe('AuthorizeHandler integration', () => { const request = new Request({ body: { client_id: 12345, response_type: 'code' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -840,7 +840,7 @@ describe('AuthorizeHandler integration', () => { const request = new Request({ body: { client_id: 12345, response_type: 'code' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -870,7 +870,7 @@ describe('AuthorizeHandler integration', () => { const request = new Request({ body: { client_id: 12345, response_type: 'code' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -902,7 +902,7 @@ describe('AuthorizeHandler integration', () => { const request = new Request({ body: { client_id: 12345, response_type: 'code' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -941,7 +941,7 @@ describe('AuthorizeHandler integration', () => { redirect_uri: 'https://foobar.com', }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -976,7 +976,7 @@ describe('AuthorizeHandler integration', () => { const request = new Request({ body: { client_id: 12345 }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); try { @@ -1004,7 +1004,7 @@ describe('AuthorizeHandler integration', () => { const request = new Request({ body: { client_id: 12345 }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -1030,7 +1030,7 @@ describe('AuthorizeHandler integration', () => { const request = new Request({ body: { client_id: 12345 }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -1057,7 +1057,7 @@ describe('AuthorizeHandler integration', () => { const request = new Request({ body: { response_type: 'code' }, headers: {}, - method: {}, + method: 'ANY', query: { client_id: 12345 }, }); @@ -1087,7 +1087,7 @@ describe('AuthorizeHandler integration', () => { const request = new Request({ body: { scope: 'øå€£‰' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -1115,7 +1115,7 @@ describe('AuthorizeHandler integration', () => { const request = new Request({ body: { scope: 'foo' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -1137,7 +1137,7 @@ describe('AuthorizeHandler integration', () => { const request = new Request({ body: {}, headers: {}, - method: {}, + method: 'ANY', query: { scope: 'foo' }, }); @@ -1161,7 +1161,7 @@ describe('AuthorizeHandler integration', () => { const request = new Request({ body: {}, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -1188,7 +1188,7 @@ describe('AuthorizeHandler integration', () => { const request = new Request({ body: {}, headers: {}, - method: {}, + method: 'ANY', query: { state: 'øå€£‰' }, }); @@ -1216,7 +1216,7 @@ describe('AuthorizeHandler integration', () => { const request = new Request({ body: { state: 'foobar' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -1238,7 +1238,7 @@ describe('AuthorizeHandler integration', () => { const request = new Request({ body: {}, headers: {}, - method: {}, + method: 'ANY', query: { state: 'foobar' }, }); @@ -1262,7 +1262,7 @@ describe('AuthorizeHandler integration', () => { const request = new Request({ body: {}, headers: {}, - method: {}, + method: 'ANY', query: {}, }); const response = new Response(); @@ -1299,7 +1299,7 @@ describe('AuthorizeHandler integration', () => { const request = new Request({ body: {}, headers: { Authorization: 'Bearer foo' }, - method: {}, + method: 'ANY', query: {}, }); const response = new Response({ body: {}, headers: {} }); @@ -1421,7 +1421,7 @@ describe('AuthorizeHandler integration', () => { // const request = new Request({ // body: {}, // headers: {}, - // method: {}, + // method: 'ANY', // query: {}, // }); @@ -1448,7 +1448,7 @@ describe('AuthorizeHandler integration', () => { // const request = new Request({ // body: { response_type: 'foobar' }, // headers: {}, - // method: {}, + // method: 'ANY', // query: {}, // }); @@ -1478,7 +1478,7 @@ describe('AuthorizeHandler integration', () => { // const request = new Request({ // body: { response_type: 'code' }, // headers: {}, - // method: {}, + // method: 'ANY', // query: {}, // }); // const ResponseType = handler.getResponseType(request); @@ -1501,7 +1501,7 @@ describe('AuthorizeHandler integration', () => { // const request = new Request({ // body: {}, // headers: {}, - // method: {}, + // method: 'ANY', // query: { response_type: 'code' }, // }); // const ResponseType = handler.getResponseType(request); diff --git a/test/integration/handlers/revoke-handler.spec.ts b/test/integration/handlers/revoke-handler.spec.ts index 71e279333..362802b9b 100644 --- a/test/integration/handlers/revoke-handler.spec.ts +++ b/test/integration/handlers/revoke-handler.spec.ts @@ -88,7 +88,7 @@ describe('RevokeHandler integration', () => { const request = new Request({ body: {}, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -392,7 +392,7 @@ describe('RevokeHandler integration', () => { const request = new Request({ body: { client_id: 'øå€£‰', client_secret: 'foo' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -417,7 +417,7 @@ describe('RevokeHandler integration', () => { const request = new Request({ body: { client_id: 'foo', client_secret: 'øå€£‰' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -442,7 +442,7 @@ describe('RevokeHandler integration', () => { const request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -468,7 +468,7 @@ describe('RevokeHandler integration', () => { const request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -497,7 +497,7 @@ describe('RevokeHandler integration', () => { Buffer.from('foo:bar').toString('base64'), ), }, - method: {}, + method: 'ANY', query: {}, }); const response = new Response({ body: {}, headers: {} }); @@ -530,7 +530,7 @@ describe('RevokeHandler integration', () => { const request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -555,7 +555,7 @@ describe('RevokeHandler integration', () => { const request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -575,7 +575,7 @@ describe('RevokeHandler integration', () => { // const request = new Request({ // body: { client_id: 12345, client_secret: 'secret' }, // headers: {}, - // method: {}, + // method: 'ANY', // query: {}, // }); @@ -595,7 +595,7 @@ describe('RevokeHandler integration', () => { const request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -615,7 +615,7 @@ describe('RevokeHandler integration', () => { const request = new Request({ body: { client_secret: 'foo' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -642,7 +642,7 @@ describe('RevokeHandler integration', () => { const request = new Request({ body: { client_id: 'foo' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -675,7 +675,7 @@ describe('RevokeHandler integration', () => { Buffer.from('foo:bar').toString('base64'), ), }, - method: {}, + method: 'ANY', query: {}, }); const credentials = handler.getClientCredentials(request); @@ -696,7 +696,7 @@ describe('RevokeHandler integration', () => { const request = new Request({ body: { client_id: 'foo', client_secret: 'bar' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); const credentials = handler.getClientCredentials(request); @@ -718,7 +718,7 @@ describe('RevokeHandler integration', () => { const request = new Request({ body: {}, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -753,7 +753,7 @@ describe('RevokeHandler integration', () => { const request = new Request({ body: { token: 'hash' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -787,7 +787,7 @@ describe('RevokeHandler integration', () => { const request = new Request({ body: { token: 'hash' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -1062,7 +1062,7 @@ describe('RevokeHandler integration', () => { const request = new Request({ body: {}, headers: {}, - method: {}, + method: 'ANY', query: {}, }); diff --git a/test/integration/handlers/token-handler.spec.ts b/test/integration/handlers/token-handler.spec.ts index e7c71a629..25e861479 100755 --- a/test/integration/handlers/token-handler.spec.ts +++ b/test/integration/handlers/token-handler.spec.ts @@ -219,7 +219,7 @@ describe('TokenHandler integration', () => { const request = new Request({ body: {}, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -597,7 +597,7 @@ describe('TokenHandler integration', () => { const request = new Request({ body: { client_id: 'øå€£‰', client_secret: 'foo' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -624,7 +624,7 @@ describe('TokenHandler integration', () => { const request = new Request({ body: { client_id: 'foo', client_secret: 'øå€£‰' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -651,7 +651,7 @@ describe('TokenHandler integration', () => { const request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -681,7 +681,7 @@ describe('TokenHandler integration', () => { const request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -711,7 +711,7 @@ describe('TokenHandler integration', () => { const request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); try { @@ -741,7 +741,7 @@ describe('TokenHandler integration', () => { Buffer.from('foo:bar').toString('base64'), ), }, - method: {}, + method: 'ANY', query: {}, }); const response = new Response({ body: {}, headers: {} }); @@ -778,7 +778,7 @@ describe('TokenHandler integration', () => { const request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); try { @@ -810,7 +810,7 @@ describe('TokenHandler integration', () => { const request = new Request({ body: { client_id: 'blah', grant_type: 'password' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -851,7 +851,7 @@ describe('TokenHandler integration', () => { Buffer.from('blah:').toString('base64'), ), }, - method: {}, + method: 'ANY', query: {}, }); @@ -881,7 +881,7 @@ describe('TokenHandler integration', () => { const request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -903,7 +903,7 @@ describe('TokenHandler integration', () => { const request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -925,7 +925,7 @@ describe('TokenHandler integration', () => { const request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -947,7 +947,7 @@ describe('TokenHandler integration', () => { const request = new Request({ body: { client_secret: 'foo' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -976,7 +976,7 @@ describe('TokenHandler integration', () => { const request = new Request({ body: { client_id: 'foo' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -1007,7 +1007,7 @@ describe('TokenHandler integration', () => { const request = new Request({ body: { client_id: 'foo', grant_type: 'password' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); const credentials = handler.getClientCredentials(request); @@ -1035,7 +1035,7 @@ describe('TokenHandler integration', () => { Buffer.from('foo:bar').toString('base64'), ), }, - method: {}, + method: 'ANY', query: {}, }); const credentials = handler.getClientCredentials(request); @@ -1058,7 +1058,7 @@ describe('TokenHandler integration', () => { const request = new Request({ body: { client_id: 'foo', client_secret: 'bar' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); const credentials = handler.getClientCredentials(request); @@ -1082,7 +1082,7 @@ describe('TokenHandler integration', () => { const request = new Request({ body: {}, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -1109,7 +1109,7 @@ describe('TokenHandler integration', () => { const request = new Request({ body: { grant_type: '~foo~' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -1135,7 +1135,7 @@ describe('TokenHandler integration', () => { const request = new Request({ body: { grant_type: 'foobar' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -1165,7 +1165,7 @@ describe('TokenHandler integration', () => { const request = new Request({ body: { grant_type: 'password' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -1197,7 +1197,7 @@ describe('TokenHandler integration', () => { const request = new Request({ body: { grant_type: 'password', username: 'foo', password: 'bar' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -1250,7 +1250,7 @@ describe('TokenHandler integration', () => { grant_type: 'authorization_code', }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -1290,7 +1290,7 @@ describe('TokenHandler integration', () => { scope: 'foo', }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -1336,7 +1336,7 @@ describe('TokenHandler integration', () => { scope: 'baz', }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -1388,7 +1388,7 @@ describe('TokenHandler integration', () => { refresh_token: 12345, }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -1436,7 +1436,7 @@ describe('TokenHandler integration', () => { password: 'bar', }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); diff --git a/test/integration/request.spec.ts b/test/integration/request.spec.ts index 10273d0f0..ffe9f63c2 100755 --- a/test/integration/request.spec.ts +++ b/test/integration/request.spec.ts @@ -33,7 +33,7 @@ describe('Request integration', () => { it('should throw an error if `query` is missing', () => { try { - new Request({ body: {}, headers: {}, method: {} } as any); + new Request({ body: {}, headers: {}, method: 'ANY' } as any); should.fail('should.fail', ''); } catch (e) { @@ -46,7 +46,7 @@ describe('Request integration', () => { const request = new Request({ body: 'foo', headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -57,7 +57,7 @@ describe('Request integration', () => { const request = new Request({ body: {}, headers: { foo: 'bar', QuX: 'biz' }, - method: {}, + method: 'ANY', query: {}, }); @@ -72,14 +72,14 @@ describe('Request integration', () => { query: {}, }); - request.method.should.equal('biz'); + request.method.should.equal('BIZ'); }); it('should set the `query`', () => { const request = new Request({ body: {}, headers: {}, - method: {}, + method: 'ANY', query: 'baz', }); @@ -92,7 +92,7 @@ describe('Request integration', () => { const request = new Request({ body: {}, headers: {}, - method: {}, + method: 'ANY', query: {}, }); @@ -105,7 +105,7 @@ describe('Request integration', () => { headers: { 'content-type': 'text/html; charset=utf-8', }, - method: {}, + method: 'ANY', query: {}, }); @@ -121,7 +121,7 @@ describe('Request integration', () => { 'content-type': 'application/json', 'transfer-encoding': 'chunked', }, - method: {}, + method: 'ANY', query: {}, }); @@ -135,7 +135,7 @@ describe('Request integration', () => { 'content-type': 'application/json', 'transfer-encoding': 'chunked', }, - method: {}, + method: 'ANY', query: {}, }); @@ -149,7 +149,7 @@ describe('Request integration', () => { 'content-type': 'text/html; charset=utf-8', 'transfer-encoding': 'chunked', }, - method: {}, + method: 'ANY', query: {}, }); @@ -163,7 +163,7 @@ describe('Request integration', () => { 'content-type': 'text/html; charset=utf-8', 'transfer-encoding': 'chunked', }, - method: {}, + method: 'ANY', query: {}, }); @@ -174,7 +174,7 @@ describe('Request integration', () => { const request = new Request({ body: {}, headers: {}, - method: {}, + method: 'ANY', query: {}, }); diff --git a/test/integration/server.spec.ts b/test/integration/server.spec.ts index c1a3477a0..cda191909 100755 --- a/test/integration/server.spec.ts +++ b/test/integration/server.spec.ts @@ -49,7 +49,7 @@ describe('Server integration', () => { const request = new Request({ body: {}, headers: { Authorization: 'Bearer foo' }, - method: {}, + method: 'ANY', query: {}, }); const response = new Response({ body: {}, headers: {} }); @@ -80,7 +80,7 @@ describe('Server integration', () => { const request = new Request({ body: {}, headers: { Authorization: 'Bearer foo' }, - method: {}, + method: 'ANY', query: {}, }); const response = new Response({ body: {}, headers: {} }); @@ -102,7 +102,7 @@ describe('Server integration', () => { const request = new Request({ body: {}, headers: { Authorization: 'Bearer foo' }, - method: {}, + method: 'ANY', query: {}, }); const response = new Response({ body: {}, headers: {} }); @@ -138,7 +138,7 @@ describe('Server integration', () => { response_type: 'code', }, headers: { Authorization: 'Bearer foo' }, - method: {}, + method: 'ANY', query: { state: 'foobar' }, }); const response = new Response({ body: {}, headers: {} }); @@ -182,7 +182,7 @@ describe('Server integration', () => { response_type: 'code', }, headers: { Authorization: 'Bearer foo' }, - method: {}, + method: 'ANY', query: { state: 'foobar' }, }); @@ -221,7 +221,7 @@ describe('Server integration', () => { response_type: 'code', }, headers: { Authorization: 'Bearer foo' }, - method: {}, + method: 'ANY', query: { state: 'foobar' }, }); const response = new Response({ body: {}, headers: {} }); diff --git a/test/unit/grant-types/authorization-code-grant-type.spec.ts b/test/unit/grant-types/authorization-code-grant-type.spec.ts index 00af87de8..9ff43eaa0 100755 --- a/test/unit/grant-types/authorization-code-grant-type.spec.ts +++ b/test/unit/grant-types/authorization-code-grant-type.spec.ts @@ -27,7 +27,7 @@ describe('AuthorizationCodeGrantType', () => { const request = new Request({ body: { code: 12345 }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); const client: any = {}; diff --git a/test/unit/grant-types/password-grant-type.spec.ts b/test/unit/grant-types/password-grant-type.spec.ts index 420a232a8..c4f77beff 100755 --- a/test/unit/grant-types/password-grant-type.spec.ts +++ b/test/unit/grant-types/password-grant-type.spec.ts @@ -21,7 +21,7 @@ describe('PasswordGrantType', () => { const request = new Request({ body: { username: 'foo', password: 'bar' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); try { diff --git a/test/unit/grant-types/refresh-token-grant-type.spec.ts b/test/unit/grant-types/refresh-token-grant-type.spec.ts index 40d1d0ada..6be1ed92c 100755 --- a/test/unit/grant-types/refresh-token-grant-type.spec.ts +++ b/test/unit/grant-types/refresh-token-grant-type.spec.ts @@ -32,7 +32,7 @@ describe('RefreshTokenGrantType', () => { const request = new Request({ body: { refresh_token: 'bar' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); const client: any = {}; @@ -67,7 +67,7 @@ describe('RefreshTokenGrantType', () => { const request = new Request({ body: { refresh_token: 'bar' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); const client: any = {}; diff --git a/test/unit/handlers/authenticate-handler.spec.ts b/test/unit/handlers/authenticate-handler.spec.ts index 7b7708df9..442ef0f3c 100755 --- a/test/unit/handlers/authenticate-handler.spec.ts +++ b/test/unit/handlers/authenticate-handler.spec.ts @@ -18,7 +18,7 @@ describe('AuthenticateHandler', () => { const request = new Request({ body: {}, headers: { Authorization: 'Bearer foo' }, - method: {}, + method: 'ANY', query: {}, }); @@ -42,7 +42,7 @@ describe('AuthenticateHandler', () => { const request = new Request({ body: {}, headers: {}, - method: {}, + method: 'ANY', query: { access_token: 'foo' }, }); @@ -66,7 +66,7 @@ describe('AuthenticateHandler', () => { const request = new Request({ body: { access_token: 'foo' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); diff --git a/test/unit/handlers/authorize-handler.spec.ts b/test/unit/handlers/authorize-handler.spec.ts index ebae522c9..04c010f08 100755 --- a/test/unit/handlers/authorize-handler.spec.ts +++ b/test/unit/handlers/authorize-handler.spec.ts @@ -50,7 +50,7 @@ describe('AuthorizeHandler', () => { const request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); try { @@ -82,7 +82,7 @@ describe('AuthorizeHandler', () => { const request = new Request({ body: {}, headers: {}, - method: {}, + method: 'ANY', query: {}, }); const response = new Response(); diff --git a/test/unit/handlers/revoke-handlers.spec.ts b/test/unit/handlers/revoke-handlers.spec.ts index 007a38dba..70fc355ea 100644 --- a/test/unit/handlers/revoke-handlers.spec.ts +++ b/test/unit/handlers/revoke-handlers.spec.ts @@ -25,7 +25,7 @@ describe('RevokeHandler', () => { const request = new Request({ body: { token: 'foo' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); const client = {}; @@ -54,7 +54,7 @@ describe('RevokeHandler', () => { const request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); diff --git a/test/unit/handlers/token-handler.spec.ts b/test/unit/handlers/token-handler.spec.ts index 9316ad9fd..53fcd36cf 100755 --- a/test/unit/handlers/token-handler.spec.ts +++ b/test/unit/handlers/token-handler.spec.ts @@ -24,7 +24,7 @@ describe('TokenHandler', () => { const request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, - method: {}, + method: 'ANY', query: {}, }); From a29eb1a67183103d71e83aba2a542e90f607ae22 Mon Sep 17 00:00:00 2001 From: Ankit Date: Wed, 11 Sep 2019 18:06:39 +0530 Subject: [PATCH 23/30] updated-ts-version --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 336e247e3..500a76d1b 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1456,9 +1456,9 @@ } }, "typescript": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.2.tgz", - "integrity": "sha512-lmQ4L+J6mnu3xweP8+rOrUwzmN+MRAj7TgtJtDaXE5PMyX2kCrklhg3rvOsOIfNeAWMQWO2F1GPc1kMD2vLAfw==", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.3.tgz", + "integrity": "sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==", "dev": true }, "validate-npm-package-license": { diff --git a/package.json b/package.json index 2c8bfea0b..b4091499e 100755 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "sinon": "^7.4.2", "ts-node": "^8.3.0", "tslint": "^5.20.0", - "typescript": "^3.6.2" + "typescript": "^3.6.3" }, "license": "MIT", "engines": { From fffd5f963d0372607d772257b2331a85b5ebefcf Mon Sep 17 00:00:00 2001 From: Ankit Date: Thu, 12 Sep 2019 09:57:20 +0530 Subject: [PATCH 24/30] tsconfig-&-npm-scripts-updated --- lib/errors/oauth-error.ts | 12 +++---- lib/handlers/authenticate-handler.ts | 5 ++- package-lock.json | 9 ++---- package.json | 48 ++++++++++++++-------------- test/integration/request.spec.ts | 3 +- test/integration/response.spec.ts | 3 +- tsconfig.build.json | 3 ++ tsconfig.json | 10 +++--- 8 files changed, 43 insertions(+), 50 deletions(-) diff --git a/lib/errors/oauth-error.ts b/lib/errors/oauth-error.ts index a9ee7c842..b8334c051 100755 --- a/lib/errors/oauth-error.ts +++ b/lib/errors/oauth-error.ts @@ -1,26 +1,22 @@ -import { defaults, isEmpty } from 'lodash'; import * as statuses from 'statuses'; export class OAuthError extends Error { code: any; status: any; statusCode: any; - constructor(messageOrError: string | Error, properties?: any) { + constructor(messageOrError: string | Error, properties: any = {}) { super(); let message = messageOrError instanceof Error ? messageOrError.message : messageOrError; const error = messageOrError instanceof Error ? messageOrError : undefined; let props: any = {}; - if (!isEmpty(properties)) { - props = properties; - } - - defaults(props, { code: 500 }); + props = properties; + props.code = props.code || 500; // default code 500 if (error) { props.inner = error; } - if (isEmpty(message)) { + if (!message) { message = statuses[props.code]; } this.code = this.status = this.statusCode = props.code; diff --git a/lib/handlers/authenticate-handler.ts b/lib/handlers/authenticate-handler.ts index c9e61c52a..f880653a9 100755 --- a/lib/handlers/authenticate-handler.ts +++ b/lib/handlers/authenticate-handler.ts @@ -1,4 +1,3 @@ -import { isUndefined } from 'lodash'; import { InsufficientScopeError, InvalidArgumentError, @@ -29,13 +28,13 @@ export class AuthenticateHandler { ); } - if (options.scope && isUndefined(options.addAcceptedScopesHeader)) { + if (options.scope && options.addAcceptedScopesHeader === undefined) { throw new InvalidArgumentError( 'Missing parameter: `addAcceptedScopesHeader`', ); } - if (options.scope && isUndefined(options.addAuthorizedScopesHeader)) { + if (options.scope && options.addAuthorizedScopesHeader === undefined) { throw new InvalidArgumentError( 'Missing parameter: `addAuthorizedScopesHeader`', ); diff --git a/package-lock.json b/package-lock.json index 500a76d1b..7f4a64e25 100755 --- a/package-lock.json +++ b/package-lock.json @@ -69,12 +69,6 @@ "@types/node": "*" } }, - "@types/lodash": { - "version": "4.14.138", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.138.tgz", - "integrity": "sha512-A4uJgHz4hakwNBdHNPdxOTkYmXNgmUAKLbXZ7PKGslgeV0Mb8P3BlbYfPovExek1qnod4pDfRbxuzcVs3dlFLg==", - "dev": true - }, "@types/mocha": { "version": "5.2.7", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", @@ -688,7 +682,8 @@ "lodash": { "version": "4.17.15", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true }, "log-symbols": { "version": "2.2.0", diff --git a/package.json b/package.json index b4091499e..8c32bb540 100755 --- a/package.json +++ b/package.json @@ -43,16 +43,28 @@ } ], "main": "index.ts", - "dependencies": { - "basic-auth": "^2.0.1", - "lodash": "^4.17.15", - "statuses": "^1.5.0", - "tslib": "^1.10.0", - "type-is": "^1.6.18" + "engines": { + "node": ">=8.10" + }, + "license": "MIT", + "scripts": { + "lint": "tslint -p tsconfig.build.json -c tslint.json", + "lint:all": "tslint -p tsconfig.json -c tslint.json", + "build:clean": "npx shx rm -rf ./dist", + "build": "npx npm-run-all build:clean build:test:*", + "build:test:src": "npx tsc -p tsconfig.json", + "build:test:package": "node ./scripts/build-prod.js", + "build:prod": "npx npm-run-all build:clean build:prod:*", + "build:prod:src": "npx tsc -p tsconfig.build.json", + "build:prod:package": "node ./scripts/build-prod.js", + "test": "npx mocha 'test/**/*.spec.ts' --config 'test/.mocharc.jsonc'" + }, + "repository": { + "url": "https://github.com/oauthjs/node-oauth2-server.git", + "type": "git" }, "devDependencies": { "@types/basic-auth": "^1.1.2", - "@types/lodash": "^4.14.138", "@types/mocha": "^5.2.7", "@types/node": "^11.13.20", "@types/sinon": "^7.0.13", @@ -67,22 +79,10 @@ "tslint": "^5.20.0", "typescript": "^3.6.3" }, - "license": "MIT", - "engines": { - "node": ">=8.10" - }, - "scripts": { - "lint": "tslint -p tsconfig.build.json -c tslint.json", - "lint:all": "tslint -p tsconfig.json -c tslint.json", - "build": "npx tsc -p tsconfig.json", - "build:clean": "npx shx rm -rf ./dist", - "build:prod": "npx npm-run-all build:clean build:prod:*", - "build:prod:src": "npx tsc -p tsconfig.build.json", - "build:prod:package": "node ./scripts/build-prod.js", - "test": "npx mocha 'test/**/*.spec.ts' --config 'test/.mocharc.jsonc'" - }, - "repository": { - "type": "git", - "url": "https://github.com/oauthjs/node-oauth2-server.git" + "dependencies": { + "basic-auth": "^2.0.1", + "statuses": "^1.5.0", + "tslib": "^1.10.0", + "type-is": "^1.6.18" } } diff --git a/test/integration/request.spec.ts b/test/integration/request.spec.ts index ffe9f63c2..9515ed7b3 100755 --- a/test/integration/request.spec.ts +++ b/test/integration/request.spec.ts @@ -1,4 +1,3 @@ -import { isUndefined } from 'lodash'; import * as should from 'should'; import { InvalidArgumentError } from '../../lib/errors'; import { Request } from '../../lib/request'; @@ -96,7 +95,7 @@ describe('Request integration', () => { query: {}, }); - isUndefined(request.get('content-type')).should.be.true(); + (request.get('content-type') === undefined).should.be.true(); }); it('should return the value if the field exists', () => { diff --git a/test/integration/response.spec.ts b/test/integration/response.spec.ts index 5e29e27dd..dc73475af 100755 --- a/test/integration/response.spec.ts +++ b/test/integration/response.spec.ts @@ -1,4 +1,3 @@ -import { isUndefined } from 'lodash'; import { Response } from '../../lib/response'; /** @@ -33,7 +32,7 @@ describe('Response integration', () => { it('should return `undefined` if the field does not exist', () => { const response = new Response({ body: {}, headers: {} }); - isUndefined(response.get('content-type')).should.be.true(); + (response.get('content-type') === undefined).should.be.true(); }); it('should return the value if the field exists', () => { diff --git a/tsconfig.build.json b/tsconfig.build.json index c180ac821..634d067a8 100755 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -1,4 +1,7 @@ { "extends": "./tsconfig.json", + "compilerOptions": { + "sourceMap": false + }, "exclude": ["node_modules", "test", "**/*spec.ts"] } diff --git a/tsconfig.json b/tsconfig.json index f4afb9e1c..b42af265a 100755 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,15 +3,17 @@ "module": "commonjs", "declaration": true, "removeComments": true, - // "emitDecoratorMetadata": true, - // "experimentalDecorators": true, - "lib": ["es2017"], "target": "es2017", "sourceMap": true, "importHelpers": true, "outDir": "./dist", "baseUrl": ".", - "typeRoots": ["node_modules/@types"] + "typeRoots": ["node_modules/@types"], + "downlevelIteration": false, + "emitDecoratorMetadata": false, + "experimentalDecorators": false, + "moduleResolution": "node", + "lib": ["es2018"] }, "exclude": ["node_modules", "./dist"] } From 3ebcf71d9c11135bf8f4cd7ba22b240626eeb2fc Mon Sep 17 00:00:00 2001 From: Ankit Date: Thu, 12 Sep 2019 10:14:56 +0530 Subject: [PATCH 25/30] ignored-message-attr-in-auth-error --- lib/errors/oauth-error.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/errors/oauth-error.ts b/lib/errors/oauth-error.ts index b8334c051..3fd32de5c 100755 --- a/lib/errors/oauth-error.ts +++ b/lib/errors/oauth-error.ts @@ -21,11 +21,12 @@ export class OAuthError extends Error { } this.code = this.status = this.statusCode = props.code; this.message = message; - for (const key of Object.keys(props)) { - if (key !== 'code') { - this[key] = props[key]; - } - } + + const ignoreAttr = ['code', 'message']; + Object.keys(props) + .filter(key => !ignoreAttr.includes(key)) + .forEach(key => (this[key] = props[key])); + Error.captureStackTrace(this, OAuthError); } } From 412a3a69683e10ca81ceb589f32219a96b745691 Mon Sep 17 00:00:00 2001 From: Ankit Date: Tue, 12 Nov 2019 20:51:49 +0530 Subject: [PATCH 26/30] updated-packages --- .vscode/README.md | 2 +- CHANGELOG.md | 4 +- TODO | 4 +- lib/errors/index.ts | 4 +- lib/errors/insufficient-scope-error.ts | 2 +- lib/errors/invalid-argument-error.ts | 7 +- lib/handlers/token-handler.ts | 2 +- lib/response.ts | 6 +- package-lock.json | 405 +++++++------------------ package.json | 16 +- 10 files changed, 139 insertions(+), 313 deletions(-) diff --git a/.vscode/README.md b/.vscode/README.md index 45c60edfb..9656796b7 100644 --- a/.vscode/README.md +++ b/.vscode/README.md @@ -1,6 +1,6 @@ # VSCode Configuration -This folder contains opt-in [Workspace Settings](https://code.visualstudio.com/docs/getstarted/settings) and [Extension Recommendations](https://code.visualstudio.com/docs/editor/extension-gallery#_workspace-recommended-extensions) that the our team recommends using when working on this repository. +This folder contains opt-in [Workspace Settings](https://code.visualstudio.com/docs/getstarted/settings) and [Extension Recommendations](https://code.visualstudio.com/docs/editor/extension-gallery#_workspace-recommended-extensions) that our team recommends using when working on this repository. ## Usage diff --git a/CHANGELOG.md b/CHANGELOG.md index 889ae1df8..d24eeea71 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,10 +3,10 @@ ### 5.0.0 * BREAKING: Remove support for node v6 * BREAKING: Remove support for callbacks only support native Promises -* new: Rewrote in Typescript +* new: Rewrote in TypeScript * new: Switch from Eslint to Tslint * new: added .vscode folder for recommend extensions and recommend setting required for development -* new: added suitable Typescript Interfaces for various Objects +* new: added suitable TypeScript Interfaces for various Objects ### 4.0.0 * BREAKING: Set server_error Code to 500 diff --git a/TODO b/TODO index 205d0697f..723eeaabc 100644 --- a/TODO +++ b/TODO @@ -1,8 +1,8 @@ Todo: ✔ Add a todo - ✔ A Basic Rewrite of library in Typescript - ✔ A Basic Rewrite of tests in Typescript + ✔ A Basic Rewrite of library in TypeScript + ✔ A Basic Rewrite of tests in TypeScript ☐ Add examples for nestjs, expressjs, koa and others ☐ Add Migration guide ☐ Review all Docs diff --git a/lib/errors/index.ts b/lib/errors/index.ts index 74d0a5d5d..24141cb6f 100644 --- a/lib/errors/index.ts +++ b/lib/errors/index.ts @@ -11,6 +11,4 @@ export { ServerError } from './server-error'; export { UnauthorizedClientError } from './unauthorized-client-error'; export { UnauthorizedRequestError } from './unauthorized-request-error'; export { UnsupportedGrantTypeError } from './unsupported-grant-type-error'; -export { - UnsupportedResponseTypeError, -} from './unsupported-response-type-error'; +export { UnsupportedResponseTypeError } from './unsupported-response-type-error'; diff --git a/lib/errors/insufficient-scope-error.ts b/lib/errors/insufficient-scope-error.ts index f50b4a9e5..01f7f54aa 100755 --- a/lib/errors/insufficient-scope-error.ts +++ b/lib/errors/insufficient-scope-error.ts @@ -3,7 +3,7 @@ import { OAuthError } from './oauth-error'; /** * Constructor. * - * "The request requires higher privileges than provided by the access token.." + * "The request requires higher privileges than provided by the access token." * * @see https://tools.ietf.org/html/rfc6750.html#section-3.1 */ diff --git a/lib/errors/invalid-argument-error.ts b/lib/errors/invalid-argument-error.ts index b4c5adbd2..1730ff412 100755 --- a/lib/errors/invalid-argument-error.ts +++ b/lib/errors/invalid-argument-error.ts @@ -1,5 +1,10 @@ import { OAuthError } from './oauth-error'; - +/** + * Constructor. + * + * "The request requires valid argument." + * + */ export class InvalidArgumentError extends OAuthError { constructor(message?: string | Error, properties?: any) { super(message, { code: 500, name: 'invalid_argument', ...properties }); diff --git a/lib/handlers/token-handler.ts b/lib/handlers/token-handler.ts index 18db66155..66d9ac363 100755 --- a/lib/handlers/token-handler.ts +++ b/lib/handlers/token-handler.ts @@ -317,7 +317,7 @@ export class TokenHandler { } /** - * Given a grant type, check if client authentication is required + * Given a grant type, check if client authentication is required. */ isClientAuthenticationRequired(grantType: string) { if (Object.keys(this.requireClientAuthentication).length > 0) { diff --git a/lib/response.ts b/lib/response.ts index 52052ac5c..3128f10a0 100755 --- a/lib/response.ts +++ b/lib/response.ts @@ -7,7 +7,7 @@ export class Response { constructor(options: any = {}) { this.body = options.body || {}; this.headers = {}; - this.status = 200; + this.status = 200; // OK // Store the headers in lower case. for (const field of Object.keys(options.headers || {})) { @@ -16,7 +16,7 @@ export class Response { } } - // Store additional properties of the response object passed in + // Store additional properties of the response object passed in. for (const property of Object.keys(options)) { if (hasOwnProperty(options, property) && !this[property]) { this[property] = options[property]; @@ -38,7 +38,7 @@ export class Response { redirect(url: string) { this.set('Location', url); - this.status = 302; + this.status = 302; // Found } /** diff --git a/package-lock.json b/package-lock.json index 500a76d1b..9bde10e12 100755 --- a/package-lock.json +++ b/package-lock.json @@ -34,9 +34,9 @@ } }, "@sinonjs/formatio": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.1.tgz", - "integrity": "sha512-tsHvOB24rvyvV2+zKMmPkZ7dXX6LSLKZ7aOtXY6Edklp0uRcgGpOsQTTGTcWViFyx4uhWc6GV8QdnALbIbIdeQ==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.2.tgz", + "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", "dev": true, "requires": { "@sinonjs/commons": "^1", @@ -70,9 +70,9 @@ } }, "@types/lodash": { - "version": "4.14.138", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.138.tgz", - "integrity": "sha512-A4uJgHz4hakwNBdHNPdxOTkYmXNgmUAKLbXZ7PKGslgeV0Mb8P3BlbYfPovExek1qnod4pDfRbxuzcVs3dlFLg==", + "version": "4.14.146", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.146.tgz", + "integrity": "sha512-JzJcmQ/ikHSv7pbvrVNKJU5j9jL9VLf3/gqs048CEnBVVVEv4kve3vLxoPHGvclutS+Il4SBIuQQ087m1eHffw==", "dev": true }, "@types/mocha": { @@ -82,15 +82,15 @@ "dev": true }, "@types/node": { - "version": "11.13.20", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.20.tgz", - "integrity": "sha512-JE0UpLWZTV1sGcaj0hN+Q0760OEjpgyFJ06DOMVW6qKBducKdJQaIw0TGL6ccj7VXRduIOHLWQi+tHwulZJHVQ==", + "version": "11.15.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.15.2.tgz", + "integrity": "sha512-BqCU9uIFkUH9Sgo2uLYbmIiFB1T+VBiM8AI/El3LIAI5KzwtckeSG+3WOYZr9aMoX4UIvRFBWBeSaOu6hFue2Q==", "dev": true }, "@types/sinon": { - "version": "7.0.13", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-7.0.13.tgz", - "integrity": "sha512-d7c/C/+H/knZ3L8/cxhicHUiTDxdgap0b/aNJfsmLwFu/iOP17mdgbQsbHA3SJmrzsjD0l3UEE5SN4xxuz5ung==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-7.5.0.tgz", + "integrity": "sha512-NyzhuSBy97B/zE58cDw4NyGvByQbAHNP9069KVSgnXt/sc0T6MFRh0InKAeBVHJWdSXG1S3+PxgVIgKo9mTHbw==", "dev": true }, "@types/statuses": { @@ -130,9 +130,9 @@ } }, "arg": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.0.tgz", - "integrity": "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.1.tgz", + "integrity": "sha512-SlmP3fEA88MBv0PypnXZ8ZfJhwmDeIE3SP71j37AiXQBXYosPV0x6uISAaHYSlSVhmHOVkomen0tbGk6Anlebw==", "dev": true }, "argparse": { @@ -248,22 +248,44 @@ } }, "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", "dev": true, "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } } }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true - }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -280,9 +302,9 @@ "dev": true }, "commander": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", - "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, "concat-map": { @@ -340,15 +362,6 @@ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, - "end-of-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -407,21 +420,6 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, "find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", @@ -458,15 +456,6 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, "glob": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", @@ -548,12 +537,6 @@ "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", "dev": true }, - "invert-kv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", - "dev": true - }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -561,9 +544,9 @@ "dev": true }, "is-buffer": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", - "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", "dev": true }, "is-callable": { @@ -593,12 +576,6 @@ "has": "^1.0.1" } }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true - }, "is-symbol": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", @@ -654,15 +631,6 @@ "integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==", "dev": true }, - "lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", - "dev": true, - "requires": { - "invert-kv": "^2.0.0" - } - }, "load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", @@ -711,31 +679,11 @@ "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", "dev": true }, - "map-age-cleaner": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", - "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", - "dev": true, - "requires": { - "p-defer": "^1.0.0" - } - }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, - "mem": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", - "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", - "dev": true, - "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^2.0.0", - "p-is-promise": "^2.0.0" - } - }, "memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", @@ -755,12 +703,6 @@ "mime-db": "1.40.0" } }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -786,9 +728,9 @@ } }, "mocha": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.0.tgz", - "integrity": "sha512-qwfFgY+7EKAAUAdv7VYMZQknI7YJSGesxHyhn6qD52DV8UcSZs5XwCifcZGMVIE4a5fbmhvbotxC0DLQ0oKohQ==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.2.tgz", + "integrity": "sha512-FgDS9Re79yU1xz5d+C4rv1G7QagNGHZ+iXF81hO8zY35YZZcLEsJVfFolfsqKFWunATEvNzMK0r/CwWd/szO9A==", "dev": true, "requires": { "ansi-colors": "3.2.3", @@ -811,9 +753,9 @@ "supports-color": "6.0.0", "which": "1.3.1", "wide-align": "1.1.3", - "yargs": "13.2.2", - "yargs-parser": "13.0.0", - "yargs-unparser": "1.5.0" + "yargs": "13.3.0", + "yargs-parser": "13.1.1", + "yargs-unparser": "1.6.0" } }, "ms": { @@ -880,21 +822,6 @@ "string.prototype.padend": "^3.0.0" } }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -932,35 +859,6 @@ "wrappy": "1" } }, - "os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", - "dev": true, - "requires": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" - } - }, - "p-defer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", - "dev": true - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true - }, - "p-is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", - "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", - "dev": true - }, "p-limit": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", @@ -1020,9 +918,9 @@ "dev": true }, "path-to-regexp": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", - "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", "dev": true, "requires": { "isarray": "0.0.1" @@ -1049,16 +947,6 @@ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -1228,16 +1116,10 @@ } } }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true - }, "sinon": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.4.2.tgz", - "integrity": "sha512-pY5RY99DKelU3pjNxcWo6XqeB1S118GBcVIIdDi6V+h6hevn1izcg2xv1hTHW/sViRXU7sUOxt4wTUJ3gsW2CQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.5.0.tgz", + "integrity": "sha512-AoD0oJWerp0/rY9czP/D6hDTTUYGpObhZjMpd7Cl/A6+j0xBE+ayL/ldfggkBXUs0IkvIiM1ljM8+WkOc5k78Q==", "dev": true, "requires": { "@sinonjs/commons": "^1.4.0", @@ -1267,9 +1149,9 @@ "dev": true }, "source-map-support": { - "version": "0.5.12", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", - "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", + "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -1355,12 +1237,6 @@ "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true - }, "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -1377,9 +1253,9 @@ } }, "ts-node": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.3.0.tgz", - "integrity": "sha512-dyNS/RqyVTDcmNM4NIBAeDMpsAdaQ+ojdf0GOLqE6nwJOgzEkdRNzJywhDfwnuvB10oa6NLVG1rUJQCpRN7qoQ==", + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.5.0.tgz", + "integrity": "sha512-fbG32iZEupNV2E2Fd2m2yt1TdAwR3GTCrJQBHDevIiEBNy1A8kqnyl1fv7jmRmmbtcapFab2glZXHJvfD1ed0Q==", "dev": true, "requires": { "arg": "^4.1.0", @@ -1403,9 +1279,9 @@ "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" }, "tslint": { - "version": "5.20.0", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.0.tgz", - "integrity": "sha512-2vqIvkMHbnx8acMogAERQ/IuINOq6DFqgF8/VDvhEkBqQh/x6SP0Y+OHnKth9/ZcHQSroOZwUQSN18v8KKF0/g==", + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.1.tgz", + "integrity": "sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -1456,9 +1332,9 @@ } }, "typescript": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.3.tgz", - "integrity": "sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.2.tgz", + "integrity": "sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ==", "dev": true }, "validate-npm-package-license": { @@ -1496,48 +1372,40 @@ } }, "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" }, "dependencies": { "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" } }, "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "^4.1.0" } } } @@ -1555,22 +1423,21 @@ "dev": true }, "yargs": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz", - "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", + "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", "dev": true, "requires": { - "cliui": "^4.0.0", + "cliui": "^5.0.0", "find-up": "^3.0.0", "get-caller-file": "^2.0.1", - "os-locale": "^3.1.0", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^3.0.0", "which-module": "^2.0.0", "y18n": "^4.0.0", - "yargs-parser": "^13.0.0" + "yargs-parser": "^13.1.1" }, "dependencies": { "ansi-regex": { @@ -1602,9 +1469,9 @@ } }, "yargs-parser": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.0.0.tgz", - "integrity": "sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==", + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", + "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", "dev": true, "requires": { "camelcase": "^5.0.0", @@ -1612,64 +1479,20 @@ } }, "yargs-unparser": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.5.0.tgz", - "integrity": "sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", "dev": true, "requires": { "flat": "^4.1.0", - "lodash": "^4.17.11", - "yargs": "^12.0.5" - }, - "dependencies": { - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true - }, - "yargs": { - "version": "12.0.5", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", - "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", - "dev": true, - "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.2.0", - "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^11.1.1" - } - }, - "yargs-parser": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", - "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } + "lodash": "^4.17.15", + "yargs": "^13.3.0" } }, "yn": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.0.tgz", - "integrity": "sha512-kKfnnYkbTfrAdd0xICNFw7Atm8nKpLcLv9AZGEt+kczL/WQVai4e2V6ZN8U/O+iI6WrNuJjNNOyu4zfhl9D3Hg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true } } diff --git a/package.json b/package.json index b4091499e..fe0cd95ed 100755 --- a/package.json +++ b/package.json @@ -52,20 +52,20 @@ }, "devDependencies": { "@types/basic-auth": "^1.1.2", - "@types/lodash": "^4.14.138", + "@types/lodash": "^4.14.146", "@types/mocha": "^5.2.7", - "@types/node": "^11.13.20", - "@types/sinon": "^7.0.13", + "@types/node": "^11.15.2", + "@types/sinon": "^7.5.0", "@types/statuses": "^1.5.0", "@types/type-is": "^1.6.3", - "mocha": "^6.2.0", + "mocha": "^6.2.2", "npm-run-all": "^4.1.5", "should": "^13.2.3", "shx": "^0.3.2", - "sinon": "^7.4.2", - "ts-node": "^8.3.0", - "tslint": "^5.20.0", - "typescript": "^3.6.3" + "sinon": "^7.5.0", + "ts-node": "^8.5.0", + "tslint": "^5.20.1", + "typescript": "^3.7.2" }, "license": "MIT", "engines": { From 33663967551456f00597fc0d589cb593d35f04fe Mon Sep 17 00:00:00 2001 From: Ankit Date: Fri, 29 Nov 2019 23:53:18 +0530 Subject: [PATCH 27/30] listed NodeJS 12 in travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 0235c3573..9d70f4ecd 100755 --- a/.travis.yml +++ b/.travis.yml @@ -3,5 +3,6 @@ language: node_js node_js: - 8 - 10 + - 12 sudo: false From 98d1dcbf07b428a55ff2cf515397b9b14496f84f Mon Sep 17 00:00:00 2001 From: Ankit Date: Sat, 30 Nov 2019 09:48:25 +0530 Subject: [PATCH 28/30] fixing-for-strict-null-checks --- lib/errors/access-denied-error.ts | 2 +- lib/errors/insufficient-scope-error.ts | 2 +- lib/errors/invalid-argument-error.ts | 2 +- lib/errors/invalid-client-error.ts | 2 +- lib/errors/invalid-grant-error.ts | 2 +- lib/errors/invalid-request-error.ts | 2 +- lib/errors/invalid-scope-error.ts | 2 +- lib/errors/invalid-token-error.ts | 2 +- lib/errors/server-error.ts | 2 +- lib/errors/unauthorized-client-error.ts | 5 +---- lib/errors/unauthorized-request-error.ts | 2 +- lib/errors/unsupported-grant-type-error.ts | 2 +- lib/errors/unsupported-response-type-error.ts | 2 +- lib/handlers/authenticate-handler.ts | 2 +- lib/types/falsy.type.ts | 1 - 15 files changed, 14 insertions(+), 18 deletions(-) delete mode 100644 lib/types/falsy.type.ts diff --git a/lib/errors/access-denied-error.ts b/lib/errors/access-denied-error.ts index 681ca61ab..d78e98d00 100755 --- a/lib/errors/access-denied-error.ts +++ b/lib/errors/access-denied-error.ts @@ -9,7 +9,7 @@ import { OAuthError } from './oauth-error'; */ export class AccessDeniedError extends OAuthError { - constructor(message?: string | Error, properties?: any) { + constructor(message: string | Error = '', properties?: any) { super(message, { code: 400, name: 'access_denied', ...properties }); } } diff --git a/lib/errors/insufficient-scope-error.ts b/lib/errors/insufficient-scope-error.ts index 01f7f54aa..c63720a62 100755 --- a/lib/errors/insufficient-scope-error.ts +++ b/lib/errors/insufficient-scope-error.ts @@ -9,7 +9,7 @@ import { OAuthError } from './oauth-error'; */ export class InsufficientScopeError extends OAuthError { - constructor(message?: string | Error, properties?: any) { + constructor(message: string | Error = '', properties?: any) { super(message, { code: 403, name: 'insufficient_scope', ...properties }); } } diff --git a/lib/errors/invalid-argument-error.ts b/lib/errors/invalid-argument-error.ts index 1730ff412..393dee964 100755 --- a/lib/errors/invalid-argument-error.ts +++ b/lib/errors/invalid-argument-error.ts @@ -6,7 +6,7 @@ import { OAuthError } from './oauth-error'; * */ export class InvalidArgumentError extends OAuthError { - constructor(message?: string | Error, properties?: any) { + constructor(message: string | Error = '', properties?: any) { super(message, { code: 500, name: 'invalid_argument', ...properties }); } } diff --git a/lib/errors/invalid-client-error.ts b/lib/errors/invalid-client-error.ts index 8c296a2d0..1b097b046 100755 --- a/lib/errors/invalid-client-error.ts +++ b/lib/errors/invalid-client-error.ts @@ -10,7 +10,7 @@ import { OAuthError } from './oauth-error'; */ export class InvalidClientError extends OAuthError { - constructor(message?: string | Error, properties?: any) { + constructor(message: string | Error = '', properties?: any) { super(message, { code: 400, name: 'invalid_client', ...properties }); } } diff --git a/lib/errors/invalid-grant-error.ts b/lib/errors/invalid-grant-error.ts index 8c37a3c9f..046ef1902 100755 --- a/lib/errors/invalid-grant-error.ts +++ b/lib/errors/invalid-grant-error.ts @@ -11,7 +11,7 @@ import { OAuthError } from './oauth-error'; */ export class InvalidGrantError extends OAuthError { - constructor(message?: string | Error, properties?: any) { + constructor(message: string | Error = '', properties?: any) { super(message, { code: 400, name: 'invalid_grant', ...properties }); } } diff --git a/lib/errors/invalid-request-error.ts b/lib/errors/invalid-request-error.ts index 6fe0a253b..d77b8683b 100755 --- a/lib/errors/invalid-request-error.ts +++ b/lib/errors/invalid-request-error.ts @@ -10,7 +10,7 @@ import { OAuthError } from './oauth-error'; */ export class InvalidRequestError extends OAuthError { - constructor(message?: string | Error, properties?: any) { + constructor(message: string | Error = '', properties?: any) { super(message, { code: 400, name: 'invalid_request', ...properties }); } } diff --git a/lib/errors/invalid-scope-error.ts b/lib/errors/invalid-scope-error.ts index 6f3977fb5..9611d3aac 100755 --- a/lib/errors/invalid-scope-error.ts +++ b/lib/errors/invalid-scope-error.ts @@ -9,7 +9,7 @@ import { OAuthError } from './oauth-error'; */ export class InvalidScopeError extends OAuthError { - constructor(message?: string | Error, properties?: any) { + constructor(message: string | Error = '', properties?: any) { super(message, { code: 400, name: 'invalid_scope', ...properties }); } } diff --git a/lib/errors/invalid-token-error.ts b/lib/errors/invalid-token-error.ts index ea22f3012..13f8a97df 100755 --- a/lib/errors/invalid-token-error.ts +++ b/lib/errors/invalid-token-error.ts @@ -9,7 +9,7 @@ import { OAuthError } from './oauth-error'; */ export class InvalidTokenError extends OAuthError { - constructor(message?: string | Error, properties?: any) { + constructor(message: string | Error = '', properties?: any) { super(message, { code: 401, name: 'invalid_token', ...properties }); } } diff --git a/lib/errors/server-error.ts b/lib/errors/server-error.ts index 68db41ef4..bccc22a41 100755 --- a/lib/errors/server-error.ts +++ b/lib/errors/server-error.ts @@ -9,7 +9,7 @@ import { OAuthError } from './oauth-error'; */ export class ServerError extends OAuthError { - constructor(message?: string | Error, properties?: any) { + constructor(message: string | Error = '', properties?: any) { super(message, { code: 500, name: 'server_error', ...properties }); } } diff --git a/lib/errors/unauthorized-client-error.ts b/lib/errors/unauthorized-client-error.ts index 87d7167b7..14f3fa0ae 100755 --- a/lib/errors/unauthorized-client-error.ts +++ b/lib/errors/unauthorized-client-error.ts @@ -9,10 +9,7 @@ import { OAuthError } from './oauth-error'; */ export class UnauthorizedClientError extends OAuthError { - constructor( - message?: string | Error, - properties?: { code?: number; message?: string }, - ) { + constructor(message: string | Error = '', properties?: any) { super(message, { code: 400, name: 'unauthorized_client', ...properties }); } } diff --git a/lib/errors/unauthorized-request-error.ts b/lib/errors/unauthorized-request-error.ts index ba98235da..2de582c43 100755 --- a/lib/errors/unauthorized-request-error.ts +++ b/lib/errors/unauthorized-request-error.ts @@ -12,7 +12,7 @@ import { OAuthError } from './oauth-error'; */ export class UnauthorizedRequestError extends OAuthError { - constructor(message?: string | Error, properties?: any) { + constructor(message: string | Error = '', properties?: any) { super(message, { code: 401, name: 'unauthorized_request', ...properties }); } } diff --git a/lib/errors/unsupported-grant-type-error.ts b/lib/errors/unsupported-grant-type-error.ts index 7a249d343..5174c6bce 100755 --- a/lib/errors/unsupported-grant-type-error.ts +++ b/lib/errors/unsupported-grant-type-error.ts @@ -9,7 +9,7 @@ import { OAuthError } from './oauth-error'; */ export class UnsupportedGrantTypeError extends OAuthError { - constructor(message?: string | Error, properties?: any) { + constructor(message: string | Error = '', properties?: any) { super(message, { code: 400, name: 'unsupported_grant_type', diff --git a/lib/errors/unsupported-response-type-error.ts b/lib/errors/unsupported-response-type-error.ts index 14c3f9e83..9e7cd1471 100755 --- a/lib/errors/unsupported-response-type-error.ts +++ b/lib/errors/unsupported-response-type-error.ts @@ -10,7 +10,7 @@ import { OAuthError } from './oauth-error'; */ export class UnsupportedResponseTypeError extends OAuthError { - constructor(message?: string | Error, properties?: any) { + constructor(message: string | Error = '', properties?: any) { super(message, { code: 400, name: 'unsupported_response_type', diff --git a/lib/handlers/authenticate-handler.ts b/lib/handlers/authenticate-handler.ts index f880653a9..67b30b0a1 100755 --- a/lib/handlers/authenticate-handler.ts +++ b/lib/handlers/authenticate-handler.ts @@ -114,7 +114,7 @@ export class AuthenticateHandler { const queryToken = request.query.access_token; const bodyToken = request.body.access_token; - if ((!!headerToken && 1) + (!!queryToken && 1) + (!!bodyToken && 1) > 1) { + if ([headerToken, queryToken, bodyToken].filter(Boolean).length > 1) { throw new InvalidRequestError( 'Invalid request: only one authentication method is allowed', ); diff --git a/lib/types/falsy.type.ts b/lib/types/falsy.type.ts deleted file mode 100644 index eb543423d..000000000 --- a/lib/types/falsy.type.ts +++ /dev/null @@ -1 +0,0 @@ -type Falsy = '' | 0 | false | null | undefined; From b1bab594087eafd7e9d0163e91f8c78250600523 Mon Sep 17 00:00:00 2001 From: Ankit Date: Sat, 30 Nov 2019 10:53:33 +0530 Subject: [PATCH 29/30] moved-required-@types to-dependencies-from-devDependencies --- package-lock.json | 30 +++++++++++++----------------- package.json | 12 ++++++------ 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/package-lock.json b/package-lock.json index ef1f618e1..9d7156cac 100755 --- a/package-lock.json +++ b/package-lock.json @@ -64,7 +64,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@types/basic-auth/-/basic-auth-1.1.2.tgz", "integrity": "sha512-NzkkcC+gkkILWaBi3+/z/3do6Ybk6TWeTqV5zCVXmG2KaBoT5YqlJvfqP44HCyDA+Cu58pp7uKAxy/G58se/TA==", - "dev": true, "requires": { "@types/node": "*" } @@ -76,28 +75,25 @@ "dev": true }, "@types/node": { - "version": "11.15.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.15.2.tgz", - "integrity": "sha512-BqCU9uIFkUH9Sgo2uLYbmIiFB1T+VBiM8AI/El3LIAI5KzwtckeSG+3WOYZr9aMoX4UIvRFBWBeSaOu6hFue2Q==", - "dev": true + "version": "11.15.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.15.3.tgz", + "integrity": "sha512-5RzvXVietaB8S4dwDjxjltAOHtTO87fiksjqjWGZih97j6KSrdCDaRfmYMNrgrLM87odGBrsTHAl6N3fLraQaw==" }, "@types/sinon": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-7.5.0.tgz", - "integrity": "sha512-NyzhuSBy97B/zE58cDw4NyGvByQbAHNP9069KVSgnXt/sc0T6MFRh0InKAeBVHJWdSXG1S3+PxgVIgKo9mTHbw==", + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-7.5.1.tgz", + "integrity": "sha512-EZQUP3hSZQyTQRfiLqelC9NMWd1kqLcmQE0dMiklxBkgi84T+cHOhnKpgk4NnOWpGX863yE6+IaGnOXUNFqDnQ==", "dev": true }, "@types/statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-4zJN5gJH+Km6hA36z8MnOKas6EU0qwxItTXNijYDPuZUsSk4EpIAB56fwnxZIhi3tHx42J7wqNdQTqt49Ar9FQ==", - "dev": true + "integrity": "sha512-4zJN5gJH+Km6hA36z8MnOKas6EU0qwxItTXNijYDPuZUsSk4EpIAB56fwnxZIhi3tHx42J7wqNdQTqt49Ar9FQ==" }, "@types/type-is": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/@types/type-is/-/type-is-1.6.3.tgz", "integrity": "sha512-PNs5wHaNcBgCQG5nAeeZ7OvosrEsI9O4W2jAOO9BCCg4ux9ZZvH2+0iSCOIDBiKuQsiNS8CBlmfX9f5YBQ22cA==", - "dev": true, "requires": { "@types/node": "*" } @@ -124,9 +120,9 @@ } }, "arg": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.1.tgz", - "integrity": "sha512-SlmP3fEA88MBv0PypnXZ8ZfJhwmDeIE3SP71j37AiXQBXYosPV0x6uISAaHYSlSVhmHOVkomen0tbGk6Anlebw==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.2.tgz", + "integrity": "sha512-+ytCkGcBtHZ3V2r2Z06AncYO8jz46UEamcspGoU8lHcEbpn6J77QK0vdWvChsclg/tM5XIJC5tnjmPp7Eq6Obg==", "dev": true }, "argparse": { @@ -1248,9 +1244,9 @@ } }, "ts-node": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.5.0.tgz", - "integrity": "sha512-fbG32iZEupNV2E2Fd2m2yt1TdAwR3GTCrJQBHDevIiEBNy1A8kqnyl1fv7jmRmmbtcapFab2glZXHJvfD1ed0Q==", + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.5.4.tgz", + "integrity": "sha512-izbVCRV68EasEPQ8MSIGBNK9dc/4sYJJKYA+IarMQct1RtEot6Xp0bXuClsbUSnKpg50ho+aOAx8en5c+y4OFw==", "dev": true, "requires": { "arg": "^4.1.0", diff --git a/package.json b/package.json index 92e111c81..b26328d1f 100755 --- a/package.json +++ b/package.json @@ -64,22 +64,22 @@ "type": "git" }, "devDependencies": { - "@types/basic-auth": "^1.1.2", "@types/mocha": "^5.2.7", - "@types/node": "^11.15.2", - "@types/sinon": "^7.5.0", - "@types/statuses": "^1.5.0", - "@types/type-is": "^1.6.3", + "@types/sinon": "^7.5.1", "mocha": "^6.2.2", "npm-run-all": "^4.1.5", "should": "^13.2.3", "shx": "^0.3.2", "sinon": "^7.5.0", - "ts-node": "^8.5.0", + "ts-node": "^8.5.4", "tslint": "^5.20.1", "typescript": "^3.7.2" }, "dependencies": { + "@types/basic-auth": "^1.1.2", + "@types/node": "^11.15.3", + "@types/statuses": "^1.5.0", + "@types/type-is": "^1.6.3", "basic-auth": "^2.0.1", "statuses": "^1.5.0", "tslib": "^1.10.0", From 52120fdedda1dc02ec083ec50fae44154675c301 Mon Sep 17 00:00:00 2001 From: Ankit Date: Thu, 5 Dec 2019 16:23:54 +0530 Subject: [PATCH 30/30] enabled-sourcemap --- tsconfig.build.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.build.json b/tsconfig.build.json index 634d067a8..56ee33060 100755 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -1,7 +1,7 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "sourceMap": false + "sourceMap": true }, "exclude": ["node_modules", "test", "**/*spec.ts"] }