diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 000000000..8e36b31a7 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,54 @@ +{ + "extends": "eslint:recommended", + "env": { + "node": true, + "mocha": true, + "es6": true + }, + "parserOptions": { + "ecmaVersion": 9, + "sourceType": "module", + "ecmaFeatures" : { + "globalReturn": false, + "impliedStrict": true, + "jsx": false + } + }, + "rules": { + "indent": [ + "error", + 2 + ], + "linebreak-style": [ + "error", + "unix" + ], + "quotes": [ + "error", + "single" + ], + "semi": [ + "error", + "always" + ], + "no-console": [ + "error" + ], + "no-var": [ + "error" + ], + "prefer-const": ["error", { + "destructuring": "any", + "ignoreReadBeforeAssign": false + }], + "no-unused-vars": [ + "error", + { + "vars": "all", + "args": "none", + "ignoreRestSiblings": false + } + ] + } +} + \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 973a5aa76..000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,2 +0,0 @@ -github: thomseddon - diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..7c49376b7 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,65 @@ + + +## Summary + + + + +## Linked issue(s) + + + + +## Involved parts of the project + + + + +## Added tests? + + + + +## OAuth2 standard + + + + +## Reproduction + + diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 473c411e8..b3e36ad14 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,20 +1,17 @@ version: 2 updates: -- package-ecosystem: npm - directory: "/" - schedule: - interval: daily - open-pull-requests-limit: 10 - ignore: - - dependency-name: sinon - versions: - - 10.0.0 - - 9.2.4 - - dependency-name: mocha - versions: - - 8.2.1 - - 8.3.0 - - 8.3.1 - - dependency-name: lodash - versions: - - 4.17.20 + - package-ecosystem: npm + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 + ignore: + - dependency-name: sinon + versions: + - 10.0.0 + - 9.2.4 + - dependency-name: mocha + versions: + - 8.2.1 + - 8.3.0 + - 8.3.1 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 000000000..7cadbb5f9 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,69 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL Semantic Analysis" + +on: + push: # all pushes + pull_request: # all PR + types: [review_requested, ready_for_review] # only non-draft PR + schedule: + - cron: '0 2 * * *' # every night at 2am + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: true + matrix: + language: [ 'javascript' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + # - name: Autobuild + # uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/tests-release.yml b/.github/workflows/tests-release.yml new file mode 100644 index 000000000..6eba05e70 --- /dev/null +++ b/.github/workflows/tests-release.yml @@ -0,0 +1,149 @@ +name: Tests for Release + +on: + push: + branches: + - release-* # all release- branches + pull_request: + # only non-draft PR and when there are "pushes" to the open PR + types: [review_requested, ready_for_review, synchronize] + branches: + - release-* # all release- branches + + +jobs: + # STEP 1 - NPM Audit + + # Before we even test a thing we want to have a clean audit! Since this is + # sufficient to be done using the lowest node version, we can easily use + # a fixed one: + + audit: + name: NPM Audit + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: '12' + - run: npm audit --production # no audit for dev dependencies + + # STEP 2 - basic unit tests + + # This is the standard unit tests as we do in the basic tests for every PR + unittest: + name: Basic unit tests + runs-on: ubuntu-latest + needs: [audit] + strategy: + matrix: + node: [12, 14, 16] + steps: + - name: Checkout ${{ matrix.node }} + uses: actions/checkout@v2 + + - name: Setup node ${{ matrix.node }} + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node }} + + - name: Cache dependencies ${{ matrix.node }} + uses: actions/cache@v1 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ matrix.node }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node-${{ matrix.node }} + + # for this workflow we also require npm audit to pass + - run: npm ci + - run: npm run test:coverage + + # with the following action we enforce PRs to have a high coverage + # and ensure, changes are tested well enough so that coverage won't fail + - name: check coverage + uses: VeryGoodOpenSource/very_good_coverage@v1.2.0 + with: + path: './coverage/lcov.info' + min_coverage: 95 + + # STEP 3 - Integration tests + + # Since our release may affect several packages that depend on it we need to + # cover the closest ones, like adapters and examples. + + integrationtests: + name: Extended integration tests + runs-on: ubuntu-latest + needs: [unittest] + strategy: + matrix: + node: [12, 14] # TODO get running for node 16 + steps: + # checkout this repo + - name: Checkout ${{ matrix.node }} + uses: actions/checkout@v2 + + # checkout express-adapter repo + - name: Checkout express-adapter ${{ matrix.node }} + uses: actions/checkout@v2 + with: + repository: node-oauth/express-oauth-server + path: github/testing/express + + - name: Setup node ${{ matrix.node }} + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node }} + + - name: Cache dependencies ${{ matrix.node }} + uses: actions/cache@v1 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ matrix.node }}-node-oauth/express-oauth-server-${{ hashFiles('github/testing/express/**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node-${{ matrix.node }}-node-oauth/express-oauth-server + + # in order to test the adapter we need to use the current checkout + # and install it as local dependency + # we just cloned and install it as local dependency + - run: | + cd github/testing/express + npm ci + npm install ../../../ + npm run test + + # todo repeat with other adapters + + publish-npm-dry: + runs-on: ubuntu-latest + needs: [integrationtests] + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: 12 + registry-url: https://registry.npmjs.org/ + - run: npm ci + - run: npm publish --dry-run + env: + NODE_AUTH_TOKEN: ${{secrets.npm_token}} + + publish-github-dry: + needs: [integrationtests] + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + # we always publish targeting the lowest supported node version + node-version: 12 + registry-url: $registry-url(npm) + - run: npm ci + - run: npm publish --dry-run + env: + NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 000000000..e8b05a706 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,47 @@ +name: Tests + +# This workflow runs standard unit tests to ensure basic integrity and avoid +# regressions on pull-requests (and pushes) + +on: + push: + branches: + - master # allthough master is push protected we still keep it + - development + pull_request: # runs on all PR + branches-ignore: + - release-* # on release we run an extended workflow so no need for this + +jobs: + unittest: + name: unit tests + runs-on: ubuntu-latest + strategy: + matrix: + node: [12, 14, 16] + steps: + - name: Checkout ${{ matrix.node }} + uses: actions/checkout@v2 + + - name: Setup node ${{ matrix.node }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node }} + + - name: Cache dependencies ${{ matrix.node }} + uses: actions/cache@v1 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ matrix.node }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node-${{ matrix.node }} + - run: npm ci + - run: npm run test:coverage + + # with the following action we enforce PRs to have a high coverage + # and ensure, changes are tested well enough so that coverage won't fail + - name: check coverage + uses: VeryGoodOpenSource/very_good_coverage@v1.2.0 + with: + path: './coverage/lcov.info' + min_coverage: 95 diff --git a/.gitignore b/.gitignore index 08b44961f..0901cc5fe 100644 --- a/.gitignore +++ b/.gitignore @@ -30,7 +30,12 @@ npm-debug.log*~ auto-save-list tramp .\#* +.vscode # Org-mode .org-id-locations *_archive + +# coverage +coverage +.nyc_output 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/.mocharc.yml b/.mocharc.yml new file mode 100644 index 000000000..83fda38cf --- /dev/null +++ b/.mocharc.yml @@ -0,0 +1,8 @@ +recursive: true +reporter: "spec" +retries: 1 +slow: 20 +timeout: 2000 +ui: "bdd" +require: test/assertions +# for more options see here https://github.com/mochajs/mocha/blob/master/example/config/.mocharc.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a5896daa2..000000000 --- a/.travis.yml +++ /dev/null @@ -1,12 +0,0 @@ -language: node_js - -node_js: - - 4 - - 6 - - 8 - - 10 - - 12 - - 13 - - 14 - -sudo: false diff --git a/CHANGELOG.md b/CHANGELOG.md index d2be06839..e258b2a60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ ## Changelog +## 4.1.1 + +### Added +- Added TypeScript types +### Changed +- Removed extra files when someone npm installs. +- Upgrades all code from ES5 to ES6, where possible. + +## 4.1.0 +### Changed +* Bump dev dependencies to resolve vulnerabilities +* Replaced jshint with eslint along with should and chai +* Use sha256 when generating tokens + +### Added +* Added markdown files to discuss coding rules, commit conventions, contributing guidelines, etc. + +### Removed +* Removed lodash dependency +* Removed statuses package and use built in http.STATUS_CODES instead. + ### 4.0.0 * Bump jshint from 2.12.0 to 2.13.0 * Bump jshint from 2.12.0 to 2.13.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..9c823f752 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,167 @@ +# Contributing to @node-oauth/oauth2-server + +Thank you for your interest in this project and your aims to improving it. +This guide will give you the most important info on how to contribute properly +in order to get your pull requests accepted. + +## Disclose security vulnerabilities + +First things first: +This project has strong security implications and we appreciate every help to +improve security. + +**However, please read our [security policy](./SECURITY.md), before taking +actions.** + + + +## Guiding principles + +Before contributing to this project it is important to understand how this +project and it's collaborators views itself regarding it's scope and purpose. + +### OAuth2 standard compliance + +This project aims full standard compliance. All improvements on functionality, +as well as security implications, are done in a way that the standard remains +as the highest reference of choice. + +If you are not familiar with the OAuth2 standards, please consult at least the +following documents: + +- [RFC 6749 - The OAuth 2.0 Authorization Framework](https://datatracker.ietf.org/doc/html/rfc6749) +- [RFC 8252 - OAuth 2.0 for Native Apps](https://datatracker.ietf.org/doc/html/rfc8252) + +Extended readings: + +- [RFC 6819 - OAuth 2.0 Threat Model and Security Considerations](https://datatracker.ietf.org/doc/html/rfc6819) +- [RFC 7636 - Proof Key for Code Exchange by OAuth Public Clients](https://datatracker.ietf.org/doc/html/rfc7636) +- [RFC 7591 - OAuth 2.0 Dynamic Client Registration Protocol](https://datatracker.ietf.org/doc/html/rfc7591) + +### Framework agnostic + +Design decisions and implementations are always done with keeping in mind, that +there are multiple frameworks out there that use this project. + + + +## Development + +If you want to fix bugs or add new features, **please read this chapter and it's +sections carefully!** + +### No PR without issue + +Please make sure your commitment will be appreciated by first opening an issue +and discuss, whether this is a useful addition to the project. + +### Work on a bug or a new feature + +First, clone and install this project from source via + +```bash +$ git clone git@github.com:node-oauth/node-oauth2-server.git +$ cd node-oauth2-server +$ git checkout development # important! do not work on master! +$ npm install +``` + +From here you can run several scripts for development purposes: + +```bash +$ npm run test # runs the tests once +$ npm run test:coverage # runs the tests including coverage +$ npm run docs # generates the API docs +``` + +To work on a new feature or a fix please create a new branch: + +```bash +$ git checkout -b feature-xyz # or fix-xyz +``` + +### Coding rules + +- Unit-testing: all features or bug fixes must be tested by specs +- Documentation: all public API methods must be documented + +### Commit message convention + +We use a commit convention, inspired by [angular commit message format](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#-commit-message-format) +with ticket number at the end of summary: + +``` +(): # +``` +Summary in present tense. Not capitalized. No period at the end. +The and fields are mandatory, the () and # field is optional. + +### Run the tests before committing + +Please always make sure your code is passing linter and tests **before +committing**. By doing so you help to make reviews much easier and don't pollute +the history with commits, that are solely targeting lint fixes. + +You can run the tests via + +```bash +$ npm run test +``` + +or + +```bash +$ npm run test:coverage +``` + +to see your coverage. + +### Open a pull request (PR) + +Once you have implemented your changes and tested them locally, please open +a [pull request](https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request). + +Note: sometimes a pull request (PR) is also referred to as merge request (MR). + +#### Fundamental PR requirements + +There are a few basic requirements for your pull request to become accepted: + +- Make sure to open your pull request to target the `development` branch and not +`master` +- Make sure you are working on a branch, other than `development`; usually you + can name the branch after the feature or fix you want to provide +- Resolve any merge conflicts (usually by keeping your branch updated with + `development`) +- Have a clear description on what the PR does, including any steps necessary + for testing, reviewing, reproduction etc. +- Link to the existing issue +- Added functions or changed functions need to get documented in compliance with + JSDoc +- Make sure all CI Tests are passing + +Also make sure, to comply with the following list: + +- Do not work on `development` directly +- Do not implement multiple features in one pull request (this includes bumping + versions of dependencies that are not related to the PR/issue) +- Do not bump the release version (unless you are a maintainer) +- Do not edit the Changelog as this will be done after your PR is merged +- Do not introduce tight dependencies to a certain package that has not been + approved during the discussion in the issue + +#### Review process + +Finally your PR needs to pass the review process: + +- A certain amount of maintainers needs to review and accept your PR +- Please **expect change requests**! They will occur and are intended to improve + the overall code quality. +- If your changes have been updated please re-assign the reviewer who asked for + the changes +- Once all reviewers have approved your PR it will be merged by one of the + maintainers :tada: + +#### After merge + +Please delete your branch after merge. diff --git a/README.md b/README.md index dc2122371..95a480556 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,17 @@ -# oauth2-server - -[![npm Version][npm-image]][npm-url] -[![npm Downloads][downloads-image]][downloads-url] -[![Test Status][travis-image]][travis-url] -[![MIT Licensed][license-image]][license-url] -[![oauthjs Slack][slack-image]][slack-url] +# @node-oauth/oauth2-server Complete, compliant and well tested module for implementing an OAuth2 server in [Node.js](https://nodejs.org). -Note: After a period of hiatus, this project is now back under active maintenance. Dependencies have been updated and bug fixes will land in v3 (current master). v4 will be _mostly backwards compatible_ with no code changes required for users using a supported node release. More details in [#621](https://github.com/oauthjs/node-oauth2-server/issues/621). +NOTE: This project has been forked from [oauthjs/node-oauth2-server](https://github.com/oauthjs/node-oauth2-server) and is a continuation due to the project appearing to be abandoned. Please see [our issue board](https://github.com/node-oauth/node-oauth2-server/issues) to talk about next steps and the future of this project. ## Installation ```bash -npm install oauth2-server +npm install @node-oauth/oauth2-server ``` -The *oauth2-server* module is framework-agnostic but there are several officially supported wrappers available for popular HTTP server frameworks such as [Express](https://npmjs.org/package/express-oauth-server) and [Koa](https://npmjs.org/package/koa-oauth-server). If you're using one of those frameworks it is strongly recommended to use the respective wrapper module instead of rolling your own. +The *@node-oauth/oauth2-server* module is framework-agnostic but there are several officially supported wrappers available for popular HTTP server frameworks such as [Express](https://npmjs.org/package/express-oauth-server) and [Koa](https://npmjs.org/package/koa-oauth-server). If you're using one of those frameworks it is strongly recommended to use the respective wrapper module instead of rolling your own. ## Features @@ -26,7 +20,7 @@ The *oauth2-server* module is framework-agnostic but there are several officiall - 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. - 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). +- Complete [test suite](https://github.com/node-oauth/node-oauth2-server/tree/master/test). ## Documentation @@ -46,25 +40,16 @@ This module has been rewritten using a promise-based approach, introducing chang Please refer to our [3.0 migration guide](https://oauth2-server.readthedocs.io/en/latest/misc/migrating-v2-to-v3.html) for more information. +## Supported NodeJs versions -## Tests - -To run the test suite, install dependencies, then run `npm test`: - -```bash -npm install -npm test -``` +This project supports the node versions along the +[NodeJS LTS releases](https://nodejs.org/en/about/releases/), focusing on +- Maintenance LTS +- Active LTS +- Current -[npm-image]: https://img.shields.io/npm/v/oauth2-server.svg -[npm-url]: https://npmjs.org/package/oauth2-server -[downloads-image]: https://img.shields.io/npm/dm/oauth2-server.svg -[downloads-url]: https://npmjs.org/package/oauth2-server -[travis-image]: https://img.shields.io/travis/oauthjs/node-oauth2-server/master.svg -[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://slack.oauthjs.org/badge.svg -[slack-url]: https://slack.oauthjs.org +## Contributing to this project +Please read our [contribution guide](./CONTRIBUTING.md) before taking actions. +In any case, please open an issue before opening a pull request to find out whether your intended contribution will actually have a chance to be merged. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..f0cc8ef82 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,25 @@ +# Security Policy + +## Supported Versions + +Use this section to tell people about which versions of your project are +currently being supported with security updates. + +| Version | Supported | +| ------- | ------------------ | +| 4.x.x | :white_check_mark: | +| 3.x.x | :white_check_mark: but only very critical security issues | +| < 3 | :x: | + +## Reporting a Vulnerability + +Report security vulnerabilities to info@jankuester.com + +Please specify exactly how the vulnerability is to be exploited so we can estimate how severe the consequences can be (unless you also can specify them, too). + +Please note that we need to reproduce the vulnerability (as like with bugs) in order to safely fix it. + +A fix will be implemented in private until we can ensure the vulnerability is closed. A new release will immediately be published. +If you want to provide a fix please let us know in the e-mail so we can setup a completely private repository to work on it together. + +Finally, all security fixes will also require to pass all tests and audits. diff --git a/docs/model/overview.rst b/docs/model/overview.rst index 5e345abd0..62c9ffa99 100644 --- a/docs/model/overview.rst +++ b/docs/model/overview.rst @@ -37,6 +37,7 @@ Model functions used by the authorization code grant: - :ref:`Model#saveAuthorizationCode` - :ref:`Model#revokeAuthorizationCode` - :ref:`Model#validateScope` +- :ref:`Model#validateRedirectUri` -------- diff --git a/docs/model/spec.rst b/docs/model/spec.rst index 4cdd1fd11..953c28119 100644 --- a/docs/model/spec.rst +++ b/docs/model/spec.rst @@ -214,7 +214,7 @@ An ``Object`` representing the access token and associated data. function getAccessToken(accessToken) { // imaginary DB queries - db.queryAccessToken({access_token: accessToken}) + return db.queryAccessToken({access_token: accessToken}) .then(function(token) { return Promise.all([ token, @@ -288,7 +288,7 @@ An ``Object`` representing the refresh token and associated data. function getRefreshToken(refreshToken) { // imaginary DB queries - db.queryRefreshToken({refresh_token: refreshToken}) + return db.queryRefreshToken({refresh_token: refreshToken}) .then(function(token) { return Promise.all([ token, @@ -364,7 +364,7 @@ An ``Object`` representing the authorization code and associated data. function getAuthorizationCode(authorizationCode) { // imaginary DB queries - db.queryAuthorizationCode({authorization_code: authorizationCode}) + return db.queryAuthorizationCode({authorization_code: authorizationCode}) .then(function(code) { return Promise.all([ code, @@ -446,7 +446,7 @@ The return value (``client``) can carry additional properties that will be ignor if (clientSecret) { params.client_secret = clientSecret; } - db.queryClient(params) + return db.queryClient(params) .then(function(client) { return { id: client.id, @@ -985,3 +985,44 @@ Returns ``true`` if the access token passes, ``false`` otherwise. return requestedScopes.every(s => authorizedScopes.indexOf(s) >= 0); } +-------- + +.. _Model#validateRedirectUri: + +``validateRedirectUri(redirectUri, client, [callback])`` +================================================================ + +Invoked to check if the provided ``redirectUri`` is valid for a particular ``client``. + +This model function is **optional**. If not implemented, the ``redirectUri`` should be included in the provided ``redirectUris`` of the client. + +**Invoked during:** + +- ``authorization_code`` grant + +**Arguments:** + ++-----------------+----------+---------------------------------------------------------------------+ +| Name | Type | Description | ++=================+==========+=====================================================================+ +| redirect_uri | String | The redirect URI to validate. | ++-----------------+----------+---------------------------------------------------------------------+ +| client | Object | The associated client. | ++-----------------+----------+---------------------------------------------------------------------+ + +**Return value:** + +Returns ``true`` if the ``redirectUri`` is valid, ``false`` otherwise. + +**Remarks:** +When implementing this method you should take care of possible security risks related to ``redirectUri``. +.. _rfc6819: https://datatracker.ietf.org/doc/html/rfc6819 + +Section-5.2.3.5 is implemented by default. +.. _Section-5.2.3.5: https://datatracker.ietf.org/doc/html/rfc6819#section-5.2.3.5 + +:: + + function validateRedirectUri(redirectUri, client) { + return client.redirectUris.includes(redirectUri); + } diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 000000000..260e34aeb --- /dev/null +++ b/index.d.ts @@ -0,0 +1,476 @@ +// Type definitions for Node OAuth2 Server 4.0 +// Definitions by: Robbie Van Gorkom , +// Charles Irick , +// Daniel Fischer , +// Vitor Santos + +import * as http from 'http'; + +/** + * Represents an OAuth2 server instance. + */ +declare class OAuth2Server { + static OAuth2Server: typeof OAuth2Server; + + /** + * Instantiates OAuth2Server using the supplied model + */ + constructor(options: OAuth2Server.ServerOptions); + + /** + * Authenticates a request. + */ + authenticate( + request: OAuth2Server.Request, + response: OAuth2Server.Response, + options?: OAuth2Server.AuthenticateOptions, + callback?: OAuth2Server.Callback + ): Promise; + + /** + * Authorizes a token request. + */ + authorize( + request: OAuth2Server.Request, + response: OAuth2Server.Response, + options?: OAuth2Server.AuthorizeOptions, + callback?: OAuth2Server.Callback + ): Promise; + + /** + * Retrieves a new token for an authorized token request. + */ + token( + request: OAuth2Server.Request, + response: OAuth2Server.Response, + options?: OAuth2Server.TokenOptions, + callback?: OAuth2Server.Callback + ): Promise; +} + +declare namespace OAuth2Server { + /** + * Represents an incoming HTTP request. + */ + class Request { + body?: any; + headers?: { [key: string]: string; } | undefined; + method?: string | undefined; + query?: { [key: string]: string; } | undefined; + + /** + * Instantiates Request using the supplied options. + * + */ + constructor(options?: { [key: string]: any } | http.IncomingMessage); + + /** + * Returns the specified HTTP header field. The match is case-insensitive. + * + */ + get(field: string): any | undefined; + + /** + * Checks if the request’s Content-Type HTTP header matches any of the given MIME types. + * + */ + is(types: string[]): string | false; + } + + /** + * Represents an outgoing HTTP response. + */ + class Response { + body?: any; + headers?: { [key: string]: string; } | undefined; + status?: number | undefined; + + /** + * Instantiates Response using the supplied options. + * + */ + constructor(options?: { [key: string]: any; } | http.ServerResponse); + + /** + * Returns the specified HTTP header field. The match is case-insensitive. + * + */ + get(field: string): any | undefined; + + /** + * Sets the specified HTTP header field. The match is case-insensitive. + * + */ + set(field: string, value: string): void; + + /** + * Redirects to the specified URL using 302 Found. + * + */ + redirect(url: string): void; + } + + abstract class AbstractGrantType { + /** + * Instantiates AbstractGrantType using the supplied options. + * + */ + constructor(options: TokenOptions) + + /** + * Generate access token. Calls Model#generateAccessToken() if implemented. + * + */ + generateAccessToken(client: Client, user: User, scope: string | string[]): Promise; + + /** + * Generate refresh token. Calls Model#generateRefreshToken() if implemented. + * + */ + generateRefreshToken(client: Client, user: User, scope: string | string[]): Promise; + + /** + * Get access token expiration date. + * + */ + getAccessTokenExpiresAt(): Date; + + /** + * Get refresh token expiration date. + * + */ + getRefreshTokenExpiresAt(): Date; + + /** + * Get scope from the request body. + * + */ + getScope(request: Request): string; + + /** + * Validate requested scope. Calls Model#validateScope() if implemented. + * + */ + validateScope(user: User, client: Client, scope: string | string[]): Promise; + + /** + * Retrieve info from the request and client and return token + * + */ + abstract handle(request: Request, client: Client): Promise; + } + + interface ServerOptions extends AuthenticateOptions, AuthorizeOptions, TokenOptions { + /** + * Model object + */ + model: AuthorizationCodeModel | ClientCredentialsModel | RefreshTokenModel | PasswordModel | ExtensionModel; + } + + interface AuthenticateOptions { + /** + * The scope(s) to authenticate. + */ + scope?: string | string[] | undefined; + + /** + * Set the X-Accepted-OAuth-Scopes HTTP header on response objects. + */ + addAcceptedScopesHeader?: boolean | undefined; + + /** + * Set the X-OAuth-Scopes HTTP header on response objects. + */ + addAuthorizedScopesHeader?: boolean | undefined; + + /** + * Allow clients to pass bearer tokens in the query string of a request. + */ + allowBearerTokensInQueryString?: boolean | undefined; + } + + interface AuthorizeOptions { + /** + * The authenticate handler + */ + authenticateHandler?: {} | undefined; + + /** + * Allow clients to specify an empty state + */ + allowEmptyState?: boolean | undefined; + + /** + * Lifetime of generated authorization codes in seconds (default = 5 minutes). + */ + authorizationCodeLifetime?: number | undefined; + } + + interface TokenOptions { + /** + * Lifetime of generated access tokens in seconds (default = 1 hour) + */ + accessTokenLifetime?: number | undefined; + + /** + * Lifetime of generated refresh tokens in seconds (default = 2 weeks) + */ + refreshTokenLifetime?: number | undefined; + + /** + * Allow extended attributes to be set on the returned token + */ + allowExtendedTokenAttributes?: boolean | undefined; + + /** + * Require a client secret. Defaults to true for all grant types. + */ + requireClientAuthentication?: {} | undefined; + + /** + * Always revoke the used refresh token and issue a new one for the refresh_token grant. + */ + alwaysIssueNewRefreshToken?: boolean | undefined; + + /** + * Additional supported grant types. + */ + extendedGrantTypes?: { [key: string]: typeof AbstractGrantType } | undefined; + } + + /** + * Represents a generic callback structure for model callbacks + */ + type Callback = (err?: any, result?: T) => void; + + /** + * For returning falsey parameters in cases of failure + */ + type Falsey = '' | 0 | false | null | undefined; + + interface BaseModel { + /** + * Invoked to generate a new access token. + * + */ + generateAccessToken?(client: Client, user: User, scope: string | string[], callback?: Callback): 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, callback?: Callback): Promise; + + /** + * Invoked to save an access token and optionally a refresh token, depending on the grant type. + * + */ + saveToken(token: Token, client: Client, user: User, callback?: Callback): Promise; + } + + interface RequestAuthenticationModel { + /** + * Invoked to retrieve an existing access token previously saved through Model#saveToken(). + * + */ + getAccessToken(accessToken: string, callback?: Callback): Promise; + + /** + * Invoked during request authentication to check if the provided access token was authorized the requested scopes. + * + */ + verifyScope(token: Token, scope: string | string[], callback?: Callback): Promise; + } + + interface AuthorizationCodeModel extends BaseModel, RequestAuthenticationModel { + /** + * Invoked to generate a new refresh token. + * + */ + generateRefreshToken?(client: Client, user: User, scope: string | string[], callback?: Callback): Promise; + + /** + * Invoked to generate a new authorization code. + * + */ + generateAuthorizationCode?(client: Client, user: User, scope: string | string[], callback?: Callback): Promise; + + /** + * Invoked to retrieve an existing authorization code previously saved through Model#saveAuthorizationCode(). + * + */ + getAuthorizationCode(authorizationCode: string, callback?: Callback): Promise; + + /** + * Invoked to save an authorization code. + * + */ + saveAuthorizationCode( + code: Pick, + client: Client, + user: User, + callback?: Callback): Promise; + + /** + * Invoked to revoke an authorization code. + * + */ + revokeAuthorizationCode(code: AuthorizationCode, callback?: Callback): Promise; + + /** + * Invoked to check if the requested scope is valid for a particular client/user combination. + * + */ + validateScope?(user: User, client: Client, scope: string | string[], callback?: Callback): Promise; + } + + interface PasswordModel extends BaseModel, RequestAuthenticationModel { + /** + * Invoked to generate a new refresh token. + * + */ + generateRefreshToken?(client: Client, user: User, scope: string | string[], callback?: Callback): Promise; + + /** + * Invoked to retrieve a user using a username/password combination. + * + */ + getUser(username: string, password: string, callback?: Callback): Promise; + + /** + * Invoked to check if the requested scope is valid for a particular client/user combination. + * + */ + validateScope?(user: User, client: Client, scope: string | string[], callback?: Callback): Promise; + } + + interface RefreshTokenModel extends BaseModel, RequestAuthenticationModel { + /** + * Invoked to generate a new refresh token. + * + */ + generateRefreshToken?(client: Client, user: User, scope: string | string[], callback?: Callback): Promise; + + /** + * Invoked to retrieve an existing refresh token previously saved through Model#saveToken(). + * + */ + getRefreshToken(refreshToken: string, callback?: Callback): Promise; + + /** + * Invoked to revoke a refresh token. + * + */ + revokeToken(token: RefreshToken | Token, callback?: Callback): Promise; + } + + interface ClientCredentialsModel extends BaseModel, RequestAuthenticationModel { + /** + * Invoked to retrieve the user associated with the specified client. + * + */ + getUserFromClient(client: Client, callback?: Callback): Promise; + + /** + * Invoked to check if the requested scope is valid for a particular client/user combination. + * + */ + validateScope?(user: User, client: Client, scope: string | string[], callback?: Callback): Promise; + } + + interface ExtensionModel extends BaseModel, RequestAuthenticationModel {} + + /** + * An interface representing the user. + * A user object is completely transparent to oauth2-server and is simply used as input to model functions. + */ + interface User { + [key: string]: any; + } + + /** + * An interface representing the client and associated data + */ + interface Client { + id: string; + redirectUris?: string | string[] | undefined; + grants: string | string[]; + accessTokenLifetime?: number | undefined; + refreshTokenLifetime?: number | undefined; + [key: string]: any; + } + + /** + * An interface representing the authorization code and associated data. + */ + interface AuthorizationCode { + authorizationCode: string; + expiresAt: Date; + redirectUri: string; + scope?: string | string[] | undefined; + client: Client; + user: User; + [key: string]: any; + } + + /** + * An interface representing the token(s) and associated data. + */ + interface Token { + accessToken: string; + accessTokenExpiresAt?: Date | undefined; + refreshToken?: string | undefined; + refreshTokenExpiresAt?: Date | undefined; + scope?: string | string[] | undefined; + client: Client; + user: User; + [key: string]: any; + } + + /** + * An interface representing the refresh token and associated data. + */ + interface RefreshToken { + refreshToken: string; + refreshTokenExpiresAt?: Date | undefined; + scope?: string | string[] | undefined; + client: Client; + user: User; + [key: string]: any; + } + + class OAuthError extends Error { + constructor(messageOrError: string | Error, properties?: object); + + /** + * The HTTP error code. + */ + code: number; + + /** + * The OAuth error code. + */ + name: string; + + /** + * A human-readable error message. + */ + message: string; + } + + class AccessDeniedError extends OAuthError {} + class InsufficientScopeError extends OAuthError {} + class InvalidArgumentError extends OAuthError {} + class InvalidClientError extends OAuthError {} + class InvalidGrantError extends OAuthError {} + class InvalidRequestError extends OAuthError {} + class InvalidScopeError extends OAuthError {} + class InvalidTokenError extends OAuthError {} + class ServerError extends OAuthError {} + class UnauthorizedClientError extends OAuthError {} + class UnauthorizedRequestError extends OAuthError {} + class UnsupportedGrantTypeError extends OAuthError {} + class UnsupportedResponseTypeError extends OAuthError {} +} + +export = OAuth2Server; diff --git a/lib/errors/access-denied-error.js b/lib/errors/access-denied-error.js index d3ffc704a..ce5c0af43 100644 --- a/lib/errors/access-denied-error.js +++ b/lib/errors/access-denied-error.js @@ -4,9 +4,8 @@ * Module dependencies. */ -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); +const OAuthError = require('./oauth-error'); +const util = require('util'); /** * Constructor. @@ -17,7 +16,7 @@ var util = require('util'); */ function AccessDeniedError(message, properties) { - properties = _.assign({ + properties = Object.assign({ code: 400, name: 'access_denied' }, properties); diff --git a/lib/errors/insufficient-scope-error.js b/lib/errors/insufficient-scope-error.js index c6442eac6..a27ad6814 100644 --- a/lib/errors/insufficient-scope-error.js +++ b/lib/errors/insufficient-scope-error.js @@ -4,9 +4,8 @@ * Module dependencies. */ -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); +const OAuthError = require('./oauth-error'); +const util = require('util'); /** * Constructor. @@ -17,7 +16,7 @@ var util = require('util'); */ function InsufficientScopeError(message, properties) { - properties = _.assign({ + properties = Object.assign({ code: 403, name: 'insufficient_scope' }, properties); diff --git a/lib/errors/invalid-argument-error.js b/lib/errors/invalid-argument-error.js index cb56d5a8b..1958caa74 100644 --- a/lib/errors/invalid-argument-error.js +++ b/lib/errors/invalid-argument-error.js @@ -4,16 +4,15 @@ * Module dependencies. */ -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); +const OAuthError = require('./oauth-error'); +const util = require('util'); /** * Constructor. */ function InvalidArgumentError(message, properties) { - properties = _.assign({ + properties = Object.assign({ code: 500, name: 'invalid_argument' }, properties); diff --git a/lib/errors/invalid-client-error.js b/lib/errors/invalid-client-error.js index d95358c7c..1513d5722 100644 --- a/lib/errors/invalid-client-error.js +++ b/lib/errors/invalid-client-error.js @@ -4,9 +4,8 @@ * Module dependencies. */ -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); +const OAuthError = require('./oauth-error'); +const util = require('util'); /** * Constructor. @@ -18,7 +17,7 @@ var util = require('util'); */ function InvalidClientError(message, properties) { - properties = _.assign({ + properties = Object.assign({ code: 400, name: 'invalid_client' }, properties); diff --git a/lib/errors/invalid-grant-error.js b/lib/errors/invalid-grant-error.js index 58d032e11..2c6a568a1 100644 --- a/lib/errors/invalid-grant-error.js +++ b/lib/errors/invalid-grant-error.js @@ -4,9 +4,8 @@ * Module dependencies. */ -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); +const OAuthError = require('./oauth-error'); +const util = require('util'); /** * Constructor. @@ -19,7 +18,7 @@ var util = require('util'); */ function InvalidGrantError(message, properties) { - properties = _.assign({ + properties = Object.assign({ code: 400, name: 'invalid_grant' }, properties); diff --git a/lib/errors/invalid-request-error.js b/lib/errors/invalid-request-error.js index 4cf0a73b1..56e997ec9 100644 --- a/lib/errors/invalid-request-error.js +++ b/lib/errors/invalid-request-error.js @@ -4,9 +4,8 @@ * Module dependencies. */ -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); +const OAuthError = require('./oauth-error'); +const util = require('util'); /** * Constructor. @@ -18,7 +17,7 @@ var util = require('util'); */ function InvalidRequest(message, properties) { - properties = _.assign({ + properties = Object.assign({ code: 400, name: 'invalid_request' }, properties); diff --git a/lib/errors/invalid-scope-error.js b/lib/errors/invalid-scope-error.js index c3b287fc5..2f5746d17 100644 --- a/lib/errors/invalid-scope-error.js +++ b/lib/errors/invalid-scope-error.js @@ -4,9 +4,8 @@ * Module dependencies. */ -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); +const OAuthError = require('./oauth-error'); +const util = require('util'); /** * Constructor. @@ -17,7 +16,7 @@ var util = require('util'); */ function InvalidScopeError(message, properties) { - properties = _.assign({ + properties = Object.assign({ code: 400, name: 'invalid_scope' }, properties); diff --git a/lib/errors/invalid-token-error.js b/lib/errors/invalid-token-error.js index d7e7a8bfe..e79d9261a 100644 --- a/lib/errors/invalid-token-error.js +++ b/lib/errors/invalid-token-error.js @@ -4,9 +4,8 @@ * Module dependencies. */ -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); +const OAuthError = require('./oauth-error'); +const util = require('util'); /** * Constructor. @@ -17,7 +16,7 @@ var util = require('util'); */ function InvalidTokenError(message, properties) { - properties = _.assign({ + properties = Object.assign({ code: 401, name: 'invalid_token' }, properties); diff --git a/lib/errors/oauth-error.js b/lib/errors/oauth-error.js index cd7d33930..a96a41fee 100644 --- a/lib/errors/oauth-error.js +++ b/lib/errors/oauth-error.js @@ -3,32 +3,30 @@ /** * Module dependencies. */ -var _ = require('lodash'); -var util = require('util'); -var statuses = require('statuses'); +const util = require('util'); +const http = require('http'); /** * Constructor. */ function OAuthError(messageOrError, properties) { - var message = messageOrError instanceof Error ? messageOrError.message : messageOrError; - var error = messageOrError instanceof Error ? messageOrError : null; - if (_.isEmpty(properties)) - { + let message = messageOrError instanceof Error ? messageOrError.message : messageOrError; + const error = messageOrError instanceof Error ? messageOrError : null; + if (properties == null || !Object.entries(properties).length ) { properties = {}; } - _.defaults(properties, { code: 500 }); + properties = Object.assign({ code: 500 }, properties); if (error) { properties.inner = error; } - if (_.isEmpty(message)) { - message = statuses[properties.code]; + if (!message || message.length === 0) { + message = http.STATUS_CODES[properties.code]; } this.code = this.status = this.statusCode = properties.code; this.message = message; - for (var key in properties) { + for (const key in properties) { if (key !== 'code') { this[key] = properties[key]; } diff --git a/lib/errors/server-error.js b/lib/errors/server-error.js index d193af39c..aee958b71 100644 --- a/lib/errors/server-error.js +++ b/lib/errors/server-error.js @@ -4,9 +4,8 @@ * Module dependencies. */ -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); +const OAuthError = require('./oauth-error'); +const util = require('util'); /** * Constructor. @@ -17,7 +16,7 @@ var util = require('util'); */ function ServerError(message, properties) { - properties = _.assign({ + properties = Object.assign({ code: 503, name: 'server_error' }, properties); diff --git a/lib/errors/unauthorized-client-error.js b/lib/errors/unauthorized-client-error.js index c05075d3d..fde3cb5c5 100644 --- a/lib/errors/unauthorized-client-error.js +++ b/lib/errors/unauthorized-client-error.js @@ -4,9 +4,8 @@ * Module dependencies. */ -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); +const OAuthError = require('./oauth-error'); +const util = require('util'); /** * Constructor. @@ -17,7 +16,7 @@ var util = require('util'); */ function UnauthorizedClientError(message, properties) { - properties = _.assign({ + properties = Object.assign({ code: 400, name: 'unauthorized_client' }, properties); diff --git a/lib/errors/unauthorized-request-error.js b/lib/errors/unauthorized-request-error.js index ae7500d9c..e96048962 100644 --- a/lib/errors/unauthorized-request-error.js +++ b/lib/errors/unauthorized-request-error.js @@ -4,9 +4,8 @@ * Module dependencies. */ -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); +const OAuthError = require('./oauth-error'); +const util = require('util'); /** * Constructor. @@ -20,7 +19,7 @@ var util = require('util'); */ function UnauthorizedRequestError(message, properties) { - properties = _.assign({ + properties = Object.assign({ code: 401, name: 'unauthorized_request' }, properties); diff --git a/lib/errors/unsupported-grant-type-error.js b/lib/errors/unsupported-grant-type-error.js index 28ca0ec04..586a743bd 100644 --- a/lib/errors/unsupported-grant-type-error.js +++ b/lib/errors/unsupported-grant-type-error.js @@ -4,9 +4,8 @@ * Module dependencies. */ -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); +const OAuthError = require('./oauth-error'); +const util = require('util'); /** * Constructor. @@ -17,7 +16,7 @@ var util = require('util'); */ function UnsupportedGrantTypeError(message, properties) { - properties = _.assign({ + properties = Object.assign({ 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 index 523cc449b..539551ecd 100644 --- a/lib/errors/unsupported-response-type-error.js +++ b/lib/errors/unsupported-response-type-error.js @@ -4,9 +4,8 @@ * Module dependencies. */ -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); +const OAuthError = require('./oauth-error'); +const util = require('util'); /** * Constructor. @@ -18,7 +17,7 @@ var util = require('util'); */ function UnsupportedResponseTypeError(message, properties) { - properties = _.assign({ + properties = Object.assign({ 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 index 224a473e3..d9894b6aa 100644 --- a/lib/grant-types/abstract-grant-type.js +++ b/lib/grant-types/abstract-grant-type.js @@ -4,12 +4,12 @@ * 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'); +const InvalidArgumentError = require('../errors/invalid-argument-error'); +const InvalidScopeError = require('../errors/invalid-scope-error'); +const Promise = require('bluebird'); +const promisify = require('promisify-any').use(Promise); +const isFormat = require('@node-oauth/formats'); +const tokenUtil = require('../utils/token-util'); /** * Constructor. @@ -83,7 +83,7 @@ AbstractGrantType.prototype.getRefreshTokenExpiresAt = function() { */ AbstractGrantType.prototype.getScope = function(request) { - if (!is.nqschar(request.body.scope)) { + if (!isFormat.nqschar(request.body.scope)) { throw new InvalidArgumentError('Invalid parameter: `scope`'); } diff --git a/lib/grant-types/authorization-code-grant-type.js b/lib/grant-types/authorization-code-grant-type.js index 7eae70f8f..ed66eeab6 100644 --- a/lib/grant-types/authorization-code-grant-type.js +++ b/lib/grant-types/authorization-code-grant-type.js @@ -4,15 +4,15 @@ * 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'); +const AbstractGrantType = require('./abstract-grant-type'); +const InvalidArgumentError = require('../errors/invalid-argument-error'); +const InvalidGrantError = require('../errors/invalid-grant-error'); +const InvalidRequestError = require('../errors/invalid-request-error'); +const Promise = require('bluebird'); +const promisify = require('promisify-any').use(Promise); +const ServerError = require('../errors/server-error'); +const isFormat = require('@node-oauth/formats'); +const util = require('util'); /** * Constructor. @@ -85,7 +85,7 @@ AuthorizationCodeGrantType.prototype.getAuthorizationCode = function(request, cl throw new InvalidRequestError('Missing parameter: `code`'); } - if (!is.vschar(request.body.code)) { + if (!isFormat.vschar(request.body.code)) { throw new InvalidRequestError('Invalid parameter: `code`'); } return promisify(this.model.getAuthorizationCode, 1).call(this.model, request.body.code) @@ -114,7 +114,7 @@ AuthorizationCodeGrantType.prototype.getAuthorizationCode = function(request, cl throw new InvalidGrantError('Invalid grant: authorization code has expired'); } - if (code.redirectUri && !is.uri(code.redirectUri)) { + if (code.redirectUri && !isFormat.uri(code.redirectUri)) { throw new InvalidGrantError('Invalid grant: `redirect_uri` is not a valid URI'); } @@ -133,21 +133,21 @@ AuthorizationCodeGrantType.prototype.getAuthorizationCode = function(request, cl * @see https://tools.ietf.org/html/rfc6749#section-4.1.3 */ - AuthorizationCodeGrantType.prototype.validateRedirectUri = function(request, code) { - if (!code.redirectUri) { - return; - } +AuthorizationCodeGrantType.prototype.validateRedirectUri = function(request, code) { + if (!code.redirectUri) { + return; + } - var redirectUri = request.body.redirect_uri || request.query.redirect_uri; + 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 (!isFormat.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'); - } - }; + if (redirectUri !== code.redirectUri) { + throw new InvalidRequestError('Invalid request: `redirect_uri` is invalid'); + } +}; /** * Revoke the authorization code. @@ -175,7 +175,7 @@ AuthorizationCodeGrantType.prototype.revokeAuthorizationCode = function(code) { */ AuthorizationCodeGrantType.prototype.saveToken = function(user, client, authorizationCode, scope) { - var fns = [ + const fns = [ this.validateScope(user, client, scope), this.generateAccessToken(client, user, scope), this.generateRefreshToken(client, user, scope), @@ -186,7 +186,7 @@ AuthorizationCodeGrantType.prototype.saveToken = function(user, client, authoriz return Promise.all(fns) .bind(this) .spread(function(scope, accessToken, refreshToken, accessTokenExpiresAt, refreshTokenExpiresAt) { - var token = { + const token = { accessToken: accessToken, authorizationCode: authorizationCode, accessTokenExpiresAt: accessTokenExpiresAt, diff --git a/lib/grant-types/client-credentials-grant-type.js b/lib/grant-types/client-credentials-grant-type.js index 138333e50..d0af0fe51 100644 --- a/lib/grant-types/client-credentials-grant-type.js +++ b/lib/grant-types/client-credentials-grant-type.js @@ -4,12 +4,12 @@ * 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'); +const AbstractGrantType = require('./abstract-grant-type'); +const InvalidArgumentError = require('../errors/invalid-argument-error'); +const InvalidGrantError = require('../errors/invalid-grant-error'); +const Promise = require('bluebird'); +const promisify = require('promisify-any').use(Promise); +const util = require('util'); /** * Constructor. @@ -54,7 +54,7 @@ ClientCredentialsGrantType.prototype.handle = function(request, client) { throw new InvalidArgumentError('Missing parameter: `client`'); } - var scope = this.getScope(request); + const scope = this.getScope(request); return Promise.bind(this) .then(function() { @@ -85,7 +85,7 @@ ClientCredentialsGrantType.prototype.getUserFromClient = function(client) { */ ClientCredentialsGrantType.prototype.saveToken = function(user, client, scope) { - var fns = [ + const fns = [ this.validateScope(user, client, scope), this.generateAccessToken(client, user, scope), this.getAccessTokenExpiresAt(client, user, scope) @@ -94,7 +94,7 @@ ClientCredentialsGrantType.prototype.saveToken = function(user, client, scope) { return Promise.all(fns) .bind(this) .spread(function(scope, accessToken, accessTokenExpiresAt) { - var token = { + const token = { accessToken: accessToken, accessTokenExpiresAt: accessTokenExpiresAt, scope: scope diff --git a/lib/grant-types/password-grant-type.js b/lib/grant-types/password-grant-type.js index b7f17935b..b65f9e1f6 100644 --- a/lib/grant-types/password-grant-type.js +++ b/lib/grant-types/password-grant-type.js @@ -4,14 +4,14 @@ * 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'); +const AbstractGrantType = require('./abstract-grant-type'); +const InvalidArgumentError = require('../errors/invalid-argument-error'); +const InvalidGrantError = require('../errors/invalid-grant-error'); +const InvalidRequestError = require('../errors/invalid-request-error'); +const Promise = require('bluebird'); +const promisify = require('promisify-any').use(Promise); +const isFormat = require('@node-oauth/formats'); +const util = require('util'); /** * Constructor. @@ -56,7 +56,7 @@ PasswordGrantType.prototype.handle = function(request, client) { throw new InvalidArgumentError('Missing parameter: `client`'); } - var scope = this.getScope(request); + const scope = this.getScope(request); return Promise.bind(this) .then(function() { @@ -80,11 +80,11 @@ PasswordGrantType.prototype.getUser = function(request) { throw new InvalidRequestError('Missing parameter: `password`'); } - if (!is.uchar(request.body.username)) { + if (!isFormat.uchar(request.body.username)) { throw new InvalidRequestError('Invalid parameter: `username`'); } - if (!is.uchar(request.body.password)) { + if (!isFormat.uchar(request.body.password)) { throw new InvalidRequestError('Invalid parameter: `password`'); } @@ -103,7 +103,7 @@ PasswordGrantType.prototype.getUser = function(request) { */ PasswordGrantType.prototype.saveToken = function(user, client, scope) { - var fns = [ + const fns = [ this.validateScope(user, client, scope), this.generateAccessToken(client, user, scope), this.generateRefreshToken(client, user, scope), @@ -114,7 +114,7 @@ PasswordGrantType.prototype.saveToken = function(user, client, scope) { return Promise.all(fns) .bind(this) .spread(function(scope, accessToken, refreshToken, accessTokenExpiresAt, refreshTokenExpiresAt) { - var token = { + const token = { accessToken: accessToken, accessTokenExpiresAt: accessTokenExpiresAt, refreshToken: refreshToken, diff --git a/lib/grant-types/refresh-token-grant-type.js b/lib/grant-types/refresh-token-grant-type.js index 19f9010c2..c9a25df32 100644 --- a/lib/grant-types/refresh-token-grant-type.js +++ b/lib/grant-types/refresh-token-grant-type.js @@ -4,15 +4,15 @@ * 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'); +const AbstractGrantType = require('./abstract-grant-type'); +const InvalidArgumentError = require('../errors/invalid-argument-error'); +const InvalidGrantError = require('../errors/invalid-grant-error'); +const InvalidRequestError = require('../errors/invalid-request-error'); +const Promise = require('bluebird'); +const promisify = require('promisify-any').use(Promise); +const ServerError = require('../errors/server-error'); +const isFormat = require('@node-oauth/formats'); +const util = require('util'); /** * Constructor. @@ -82,7 +82,7 @@ RefreshTokenGrantType.prototype.getRefreshToken = function(request, client) { throw new InvalidRequestError('Missing parameter: `refresh_token`'); } - if (!is.vschar(request.body.refresh_token)) { + if (!isFormat.vschar(request.body.refresh_token)) { throw new InvalidRequestError('Invalid parameter: `refresh_token`'); } @@ -142,7 +142,7 @@ RefreshTokenGrantType.prototype.revokeToken = function(token) { */ RefreshTokenGrantType.prototype.saveToken = function(user, client, scope) { - var fns = [ + const fns = [ this.generateAccessToken(client, user, scope), this.generateRefreshToken(client, user, scope), this.getAccessTokenExpiresAt(), @@ -152,7 +152,7 @@ RefreshTokenGrantType.prototype.saveToken = function(user, client, scope) { return Promise.all(fns) .bind(this) .spread(function(accessToken, refreshToken, accessTokenExpiresAt, refreshTokenExpiresAt) { - var token = { + const token = { accessToken: accessToken, accessTokenExpiresAt: accessTokenExpiresAt, scope: scope diff --git a/lib/handlers/authenticate-handler.js b/lib/handlers/authenticate-handler.js index dc9117b27..7724742b6 100644 --- a/lib/handlers/authenticate-handler.js +++ b/lib/handlers/authenticate-handler.js @@ -4,17 +4,17 @@ * 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'); +const InvalidArgumentError = require('../errors/invalid-argument-error'); +const InvalidRequestError = require('../errors/invalid-request-error'); +const InsufficientScopeError = require('../errors/insufficient-scope-error'); +const InvalidTokenError = require('../errors/invalid-token-error'); +const OAuthError = require('../errors/oauth-error'); +const Promise = require('bluebird'); +const promisify = require('promisify-any').use(Promise); +const Request = require('../request'); +const Response = require('../response'); +const ServerError = require('../errors/server-error'); +const UnauthorizedRequestError = require('../errors/unauthorized-request-error'); /** * Constructor. @@ -90,6 +90,12 @@ AuthenticateHandler.prototype.handle = function(request, response) { // @see https://tools.ietf.org/html/rfc6750#section-3.1 if (e instanceof UnauthorizedRequestError) { response.set('WWW-Authenticate', 'Bearer realm="Service"'); + } else if (e instanceof InvalidRequestError) { + response.set('WWW-Authenticate', 'Bearer realm="Service",error="invalid_request"'); + } else if (e instanceof InvalidTokenError) { + response.set('WWW-Authenticate', 'Bearer realm="Service",error="invalid_token"'); + } else if (e instanceof InsufficientScopeError) { + response.set('WWW-Authenticate', 'Bearer realm="Service",error="insufficient_scope"'); } if (!(e instanceof OAuthError)) { @@ -109,9 +115,9 @@ AuthenticateHandler.prototype.handle = function(request, response) { */ AuthenticateHandler.prototype.getTokenFromRequest = function(request) { - var headerToken = request.get('Authorization'); - var queryToken = request.query.access_token; - var bodyToken = request.body.access_token; + const headerToken = request.get('Authorization'); + const queryToken = request.query.access_token; + const bodyToken = request.body.access_token; if (!!headerToken + !!queryToken + !!bodyToken > 1) { throw new InvalidRequestError('Invalid request: only one authentication method is allowed'); @@ -139,8 +145,8 @@ AuthenticateHandler.prototype.getTokenFromRequest = function(request) { */ AuthenticateHandler.prototype.getTokenFromRequestHeader = function(request) { - var token = request.get('Authorization'); - var matches = token.match(/Bearer\s(\S+)/); + const token = request.get('Authorization'); + const matches = token.match(/^Bearer\s(\S+)/); if (!matches) { throw new InvalidRequestError('Invalid request: malformed authorization header'); diff --git a/lib/handlers/authorize-handler.js b/lib/handlers/authorize-handler.js index 845e25b55..830bfa76d 100644 --- a/lib/handlers/authorize-handler.js +++ b/lib/handlers/authorize-handler.js @@ -4,30 +4,29 @@ * 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'); +const AccessDeniedError = require('../errors/access-denied-error'); +const AuthenticateHandler = require('../handlers/authenticate-handler'); +const InvalidArgumentError = require('../errors/invalid-argument-error'); +const InvalidClientError = require('../errors/invalid-client-error'); +const InvalidRequestError = require('../errors/invalid-request-error'); +const InvalidScopeError = require('../errors/invalid-scope-error'); +const UnsupportedResponseTypeError = require('../errors/unsupported-response-type-error'); +const OAuthError = require('../errors/oauth-error'); +const Promise = require('bluebird'); +const promisify = require('promisify-any').use(Promise); +const Request = require('../request'); +const Response = require('../response'); +const ServerError = require('../errors/server-error'); +const UnauthorizedClientError = require('../errors/unauthorized-client-error'); +const isFormat = require('@node-oauth/formats'); +const tokenUtil = require('../utils/token-util'); +const url = require('url'); /** * Response types. */ -var responseTypes = { +const responseTypes = { code: require('../response-types/code-response-type'), //token: require('../response-types/token-response-type') }; @@ -78,11 +77,11 @@ AuthorizeHandler.prototype.handle = function(request, response) { throw new InvalidArgumentError('Invalid argument: `response` must be an instance of Response'); } - if ('false' === request.query.allowed) { + if (request.query.allowed === 'false' || request.body.allowed === 'false') { return Promise.reject(new AccessDeniedError('Access denied: user denied access to application')); } - var fns = [ + const fns = [ this.getAuthorizationCodeLifetime(), this.getClient(request), this.getUser(request, response) @@ -91,14 +90,20 @@ AuthorizeHandler.prototype.handle = function(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; + const uri = this.getRedirectUri(request, client); + let scope; + let state; + let ResponseType; return Promise.bind(this) .then(function() { - var requestedScope = this.getScope(request); + state = this.getState(request); + if(request.query.allowed === 'false') { + throw new AccessDeniedError('Access denied: user denied access to application'); + } + }) + .then(function() { + const requestedScope = this.getScope(request); return this.validateScope(user, client, requestedScope); }) @@ -108,14 +113,13 @@ AuthorizeHandler.prototype.handle = function(request, response) { 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); + const responseType = new ResponseType(code.authorizationCode); + const redirectUri = this.buildSuccessRedirectUri(uri, responseType); this.updateResponse(response, redirectUri, state); @@ -125,7 +129,7 @@ AuthorizeHandler.prototype.handle = function(request, response) { if (!(e instanceof OAuthError)) { e = new ServerError(e); } - var redirectUri = this.buildErrorRedirectUri(uri, e); + const redirectUri = this.buildErrorRedirectUri(uri, e); this.updateResponse(response, redirectUri, state); @@ -150,7 +154,7 @@ AuthorizeHandler.prototype.generateAuthorizationCode = function(client, user, sc */ AuthorizeHandler.prototype.getAuthorizationCodeLifetime = function() { - var expires = new Date(); + const expires = new Date(); expires.setSeconds(expires.getSeconds() + this.authorizationCodeLifetime); return expires; @@ -161,19 +165,20 @@ AuthorizeHandler.prototype.getAuthorizationCodeLifetime = function() { */ AuthorizeHandler.prototype.getClient = function(request) { - var clientId = request.body.client_id || request.query.client_id; + const self = this; + const clientId = request.body.client_id || request.query.client_id; if (!clientId) { throw new InvalidRequestError('Missing parameter: `client_id`'); } - if (!is.vschar(clientId)) { + if (!isFormat.vschar(clientId)) { throw new InvalidRequestError('Invalid parameter: `client_id`'); } - var redirectUri = request.body.redirect_uri || request.query.redirect_uri; + const redirectUri = request.body.redirect_uri || request.query.redirect_uri; - if (redirectUri && !is.uri(redirectUri)) { + if (redirectUri && !isFormat.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) @@ -186,7 +191,7 @@ AuthorizeHandler.prototype.getClient = function(request) { throw new InvalidClientError('Invalid client: missing client `grants`'); } - if (!_.includes(client.grants, 'authorization_code')) { + if (!Array.isArray(client.grants) || !client.grants.includes('authorization_code')) { throw new UnauthorizedClientError('Unauthorized client: `grant_type` is invalid'); } @@ -194,10 +199,17 @@ AuthorizeHandler.prototype.getClient = function(request) { 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'); + if (redirectUri) { + return self.validateRedirectUri(redirectUri, client) + .then(function(valid) { + if (!valid) { + throw new InvalidClientError('Invalid client: `redirect_uri` does not match client value'); + } + return client; + }); + } else { + return client; } - return client; }); }; @@ -224,9 +236,9 @@ AuthorizeHandler.prototype.validateScope = function(user, client, scope) { */ AuthorizeHandler.prototype.getScope = function(request) { - var scope = request.body.scope || request.query.scope; + const scope = request.body.scope || request.query.scope; - if (!is.nqschar(scope)) { + if (!isFormat.nqschar(scope)) { throw new InvalidScopeError('Invalid parameter: `scope`'); } @@ -238,14 +250,15 @@ AuthorizeHandler.prototype.getScope = function(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`'); + const state = request.body.state || request.query.state; + const stateExists = state && state.length > 0; + const stateIsValid = stateExists + ? isFormat.vschar(state) + : this.allowEmptyState; + + if (!stateIsValid) { + const message = (!stateExists) ? 'Missing' : 'Invalid'; + throw new InvalidRequestError(`${message} parameter: \`state\``); } return state; @@ -281,7 +294,7 @@ AuthorizeHandler.prototype.getRedirectUri = function(request, client) { */ AuthorizeHandler.prototype.saveAuthorizationCode = function(authorizationCode, expiresAt, scope, client, redirectUri, user) { - var code = { + const code = { authorizationCode: authorizationCode, expiresAt: expiresAt, redirectUri: redirectUri, @@ -290,18 +303,26 @@ AuthorizeHandler.prototype.saveAuthorizationCode = function(authorizationCode, e return promisify(this.model.saveAuthorizationCode, 3).call(this.model, code, client, user); }; + +AuthorizeHandler.prototype.validateRedirectUri = function(redirectUri, client) { + if (this.model.validateRedirectUri) { + return promisify(this.model.validateRedirectUri, 2).call(this.model, redirectUri, client); + } + + return Promise.resolve(client.redirectUris.includes(redirectUri)); +}; /** * Get response type. */ AuthorizeHandler.prototype.getResponseType = function(request) { - var responseType = request.body.response_type || request.query.response_type; + const responseType = request.body.response_type || request.query.response_type; if (!responseType) { throw new InvalidRequestError('Missing parameter: `response_type`'); } - if (!_.has(responseTypes, responseType)) { + if (!Object.prototype.hasOwnProperty.call(responseTypes, responseType)) { throw new UnsupportedResponseTypeError('Unsupported response type: `response_type` is not supported'); } @@ -321,7 +342,7 @@ AuthorizeHandler.prototype.buildSuccessRedirectUri = function(redirectUri, respo */ AuthorizeHandler.prototype.buildErrorRedirectUri = function(redirectUri, error) { - var uri = url.parse(redirectUri); + const uri = url.parse(redirectUri); uri.query = { error: error.name diff --git a/lib/handlers/token-handler.js b/lib/handlers/token-handler.js index feaad3f54..285843e85 100644 --- a/lib/handlers/token-handler.js +++ b/lib/handlers/token-handler.js @@ -4,28 +4,27 @@ * 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'); +const BearerTokenType = require('../token-types/bearer-token-type'); +const InvalidArgumentError = require('../errors/invalid-argument-error'); +const InvalidClientError = require('../errors/invalid-client-error'); +const InvalidRequestError = require('../errors/invalid-request-error'); +const OAuthError = require('../errors/oauth-error'); +const Promise = require('bluebird'); +const promisify = require('promisify-any').use(Promise); +const Request = require('../request'); +const Response = require('../response'); +const ServerError = require('../errors/server-error'); +const TokenModel = require('../models/token-model'); +const UnauthorizedClientError = require('../errors/unauthorized-client-error'); +const UnsupportedGrantTypeError = require('../errors/unsupported-grant-type-error'); +const auth = require('basic-auth'); +const isFormat = require('@node-oauth/formats'); /** * Grant types. */ -var grantTypes = { +const 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'), @@ -56,7 +55,7 @@ function TokenHandler(options) { } this.accessTokenLifetime = options.accessTokenLifetime; - this.grantTypes = _.assign({}, grantTypes, options.extendedGrantTypes); + this.grantTypes = Object.assign({}, grantTypes, options.extendedGrantTypes); this.model = options.model; this.refreshTokenLifetime = options.refreshTokenLifetime; this.allowExtendedTokenAttributes = options.allowExtendedTokenAttributes; @@ -93,8 +92,8 @@ TokenHandler.prototype.handle = function(request, response) { return this.handleGrantType(request, client); }) .tap(function(data) { - var model = new TokenModel(data, {allowExtendedTokenAttributes: this.allowExtendedTokenAttributes}); - var tokenType = this.getTokenType(model); + const model = new TokenModel(data, {allowExtendedTokenAttributes: this.allowExtendedTokenAttributes}); + const tokenType = this.getTokenType(model); this.updateSuccessResponse(response, tokenType); }).catch(function(e) { @@ -113,8 +112,8 @@ TokenHandler.prototype.handle = function(request, response) { */ TokenHandler.prototype.getClient = function(request, response) { - var credentials = this.getClientCredentials(request); - var grantType = request.body.grant_type; + const credentials = this.getClientCredentials(request); + const grantType = request.body.grant_type; if (!credentials.clientId) { throw new InvalidRequestError('Missing parameter: `client_id`'); @@ -124,11 +123,11 @@ TokenHandler.prototype.getClient = function(request, response) { throw new InvalidRequestError('Missing parameter: `client_secret`'); } - if (!is.vschar(credentials.clientId)) { + if (!isFormat.vschar(credentials.clientId)) { throw new InvalidRequestError('Invalid parameter: `client_id`'); } - if (credentials.clientSecret && !is.vschar(credentials.clientSecret)) { + if (credentials.clientSecret && !isFormat.vschar(credentials.clientSecret)) { throw new InvalidRequestError('Invalid parameter: `client_secret`'); } @@ -173,8 +172,8 @@ TokenHandler.prototype.getClient = function(request, response) { */ TokenHandler.prototype.getClientCredentials = function(request) { - var credentials = auth(request); - var grantType = request.body.grant_type; + const credentials = auth(request); + const grantType = request.body.grant_type; if (credentials) { return { clientId: credentials.name, clientSecret: credentials.pass }; @@ -198,29 +197,29 @@ TokenHandler.prototype.getClientCredentials = function(request) { */ TokenHandler.prototype.handleGrantType = function(request, client) { - var grantType = request.body.grant_type; + const grantType = request.body.grant_type; if (!grantType) { throw new InvalidRequestError('Missing parameter: `grant_type`'); } - if (!is.nchar(grantType) && !is.uri(grantType)) { + if (!isFormat.nchar(grantType) && !isFormat.uri(grantType)) { throw new InvalidRequestError('Invalid parameter: `grant_type`'); } - if (!_.has(this.grantTypes, grantType)) { + if (!Object.prototype.hasOwnProperty.call(this.grantTypes, grantType)) { throw new UnsupportedGrantTypeError('Unsupported grant type: `grant_type` is invalid'); } - if (!_.includes(client.grants, grantType)) { + if (!Array.isArray(client.grants) || !client.grants.includes(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]; + const accessTokenLifetime = this.getAccessTokenLifetime(client); + const refreshTokenLifetime = this.getRefreshTokenLifetime(client); + const Type = this.grantTypes[grantType]; - var options = { + const options = { accessTokenLifetime: accessTokenLifetime, model: this.model, refreshTokenLifetime: refreshTokenLifetime, diff --git a/lib/models/token-model.js b/lib/models/token-model.js index c6bc3f8d4..473c7ace8 100644 --- a/lib/models/token-model.js +++ b/lib/models/token-model.js @@ -4,13 +4,13 @@ * Module dependencies. */ -var InvalidArgumentError = require('../errors/invalid-argument-error'); +const InvalidArgumentError = require('../errors/invalid-argument-error'); /** * Constructor. */ -var modelAttributes = ['accessToken', 'accessTokenExpiresAt', 'refreshToken', 'refreshTokenExpiresAt', 'scope', 'client', 'user']; +const modelAttributes = ['accessToken', 'accessTokenExpiresAt', 'refreshToken', 'refreshTokenExpiresAt', 'scope', 'client', 'user']; function TokenModel(data, options) { data = data || {}; @@ -46,8 +46,8 @@ function TokenModel(data, options) { if (options && options.allowExtendedTokenAttributes) { this.customAttributes = {}; - for (var key in data) { - if (data.hasOwnProperty(key) && (modelAttributes.indexOf(key) < 0)) { + for (const key in data) { + if ( Object.prototype.hasOwnProperty.call(data, key) && (modelAttributes.indexOf(key) < 0)) { this.customAttributes[key] = data[key]; } } diff --git a/lib/request.js b/lib/request.js index 00b879586..560b29bce 100644 --- a/lib/request.js +++ b/lib/request.js @@ -4,8 +4,8 @@ * Module dependencies. */ -var InvalidArgumentError = require('./errors/invalid-argument-error'); -var typeis = require('type-is'); +const InvalidArgumentError = require('./errors/invalid-argument-error'); +const typeis = require('type-is'); /** * Constructor. @@ -32,14 +32,14 @@ function Request(options) { this.query = options.query; // Store the headers in lower case. - for (var field in options.headers) { + for (const field in options.headers) { if (Object.prototype.hasOwnProperty.call(options.headers, field)) { this.headers[field.toLowerCase()] = options.headers[field]; } } // Store additional properties of the request object passed in - for (var property in options) { + for (const property in options) { if (Object.prototype.hasOwnProperty.call(options, property) && !this[property]) { this[property] = options[property]; } diff --git a/lib/response-types/code-response-type.js b/lib/response-types/code-response-type.js index 6eaf23a89..8252248fb 100644 --- a/lib/response-types/code-response-type.js +++ b/lib/response-types/code-response-type.js @@ -4,8 +4,8 @@ * Module dependencies. */ -var InvalidArgumentError = require('../errors/invalid-argument-error'); -var url = require('url'); +const InvalidArgumentError = require('../errors/invalid-argument-error'); +const url = require('url'); /** * Constructor. @@ -28,7 +28,7 @@ CodeResponseType.prototype.buildRedirectUri = function(redirectUri) { throw new InvalidArgumentError('Missing parameter: `redirectUri`'); } - var uri = url.parse(redirectUri, true); + const uri = url.parse(redirectUri, true); uri.query.code = this.code; uri.search = null; diff --git a/lib/response-types/token-response-type.js b/lib/response-types/token-response-type.js index 2637f64cd..29c32e709 100644 --- a/lib/response-types/token-response-type.js +++ b/lib/response-types/token-response-type.js @@ -4,7 +4,7 @@ * Module dependencies. */ -var ServerError = require('../errors/server-error'); +const ServerError = require('../errors/server-error'); /** * Constructor. diff --git a/lib/response.js b/lib/response.js index 76fe854ab..29a2c5170 100644 --- a/lib/response.js +++ b/lib/response.js @@ -12,14 +12,14 @@ function Response(options) { this.status = 200; // Store the headers in lower case. - for (var field in options.headers) { + for (const field in options.headers) { if (Object.prototype.hasOwnProperty.call(options.headers, field)) { this.headers[field.toLowerCase()] = options.headers[field]; } } // Store additional properties of the response object passed in - for (var property in options) { + for (const property in options) { if (Object.prototype.hasOwnProperty.call(options, property) && !this[property]) { this[property] = options[property]; } diff --git a/lib/server.js b/lib/server.js index fba9ccf81..53bbd2aaf 100644 --- a/lib/server.js +++ b/lib/server.js @@ -4,11 +4,10 @@ * 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'); +const AuthenticateHandler = require('./handlers/authenticate-handler'); +const AuthorizeHandler = require('./handlers/authorize-handler'); +const InvalidArgumentError = require('./errors/invalid-argument-error'); +const TokenHandler = require('./handlers/token-handler'); /** * Constructor. @@ -33,7 +32,7 @@ OAuth2Server.prototype.authenticate = function(request, response, options, callb options = {scope: options}; } - options = _.assign({ + options = Object.assign({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: true, allowBearerTokensInQueryString: false @@ -49,7 +48,7 @@ OAuth2Server.prototype.authenticate = function(request, response, options, callb */ OAuth2Server.prototype.authorize = function(request, response, options, callback) { - options = _.assign({ + options = Object.assign({ allowEmptyState: false, authorizationCodeLifetime: 5 * 60 // 5 minutes. }, this.options, options); @@ -64,7 +63,7 @@ OAuth2Server.prototype.authorize = function(request, response, options, callback */ OAuth2Server.prototype.token = function(request, response, options, callback) { - options = _.assign({ + options = Object.assign({ accessTokenLifetime: 60 * 60, // 1 hour. refreshTokenLifetime: 60 * 60 * 24 * 14, // 2 weeks. allowExtendedTokenAttributes: false, diff --git a/lib/token-types/bearer-token-type.js b/lib/token-types/bearer-token-type.js index 9124cb2e8..0bf526d0e 100644 --- a/lib/token-types/bearer-token-type.js +++ b/lib/token-types/bearer-token-type.js @@ -4,7 +4,7 @@ * Module dependencies. */ -var InvalidArgumentError = require('../errors/invalid-argument-error'); +const InvalidArgumentError = require('../errors/invalid-argument-error'); /** * Constructor. @@ -30,7 +30,7 @@ function BearerTokenType(accessToken, accessTokenLifetime, refreshToken, scope, */ BearerTokenType.prototype.valueOf = function() { - var object = { + const object = { access_token: this.accessToken, token_type: 'Bearer' }; @@ -47,8 +47,8 @@ BearerTokenType.prototype.valueOf = function() { object.scope = this.scope; } - for (var key in this.customAttributes) { - if (this.customAttributes.hasOwnProperty(key)) { + for (const key in this.customAttributes) { + if ( Object.prototype.hasOwnProperty.call(this.customAttributes, key) ) { object[key] = this.customAttributes[key]; } } diff --git a/lib/token-types/mac-token-type.js b/lib/token-types/mac-token-type.js index 9fdc600c6..a5dd240a7 100644 --- a/lib/token-types/mac-token-type.js +++ b/lib/token-types/mac-token-type.js @@ -4,7 +4,7 @@ * Module dependencies. */ -var ServerError = require('../errors/server-error'); +const ServerError = require('../errors/server-error'); /** * Constructor. diff --git a/lib/utils/token-util.js b/lib/utils/token-util.js index 0f73746bb..a66e25237 100644 --- a/lib/utils/token-util.js +++ b/lib/utils/token-util.js @@ -4,8 +4,8 @@ * Module dependencies. */ -var crypto = require('crypto'); -var randomBytes = require('bluebird').promisify(require('crypto').randomBytes); +const crypto = require('crypto'); +const randomBytes = require('bluebird').promisify(require('crypto').randomBytes); /** * Export `TokenUtil`. @@ -20,7 +20,7 @@ module.exports = { generateRandomToken: function() { return randomBytes(256).then(function(buffer) { return crypto - .createHash('sha1') + .createHash('sha256') .update(buffer) .digest('hex'); }); diff --git a/lib/validator/is.js b/lib/validator/is.js index 07af6cb64..4bb58c55a 100644 --- a/lib/validator/is.js +++ b/lib/validator/is.js @@ -1,18 +1,22 @@ 'use strict'; +/* eslint-disable no-control-regex */ /** * 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]+$/, +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]+$/, + UNICODECHARNOCRLF_EXTENDED: /^[\u{10000}-\u{10FFFF}]+$/u, URI: /^[a-zA-Z][a-zA-Z0-9+.-]+:/, VSCHAR: /^[\u0020-\u007E]+$/ }; +/* eslint-enable no-control-regex */ + /** * Export validation functions. */ @@ -57,7 +61,12 @@ module.exports = { */ uchar: function(value) { - return rules.UNICODECHARNOCRLF.test(value); + // manually test \u10000-\u10FFFF + if (rules.UNICODECHARNOCRLF.test(value)) { + return true; + } + + return rules.UNICODECHARNOCRLF_EXTENDED.test(value); }, /** diff --git a/package-lock.json b/package-lock.json index e2cb0c1b8..19a704e23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,1391 +1,2524 @@ { - "name": "oauth2-server", - "version": "4.0.0-dev.3", - "lockfileVersion": 2, + "name": "@node-oauth/oauth2-server", + "version": "4.1.1", + "lockfileVersion": 1, "requires": true, - "packages": { - "": { - "name": "oauth2-server", - "version": "4.0.0-dev.3", - "license": "MIT", - "dependencies": { - "basic-auth": "2.0.1", - "bluebird": "3.7.2", - "lodash": "4.17.21", - "promisify-any": "2.0.1", - "statuses": "1.5.0", - "type-is": "1.6.18" - }, - "devDependencies": { - "jshint": "2.13.0", - "mocha": "5.2.0", - "should": "13.2.3", - "sinon": "7.5.0" - }, - "engines": { - "node": ">=4.0" + "dependencies": { + "@babel/code-frame": { + "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", + "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.14.5" } }, - "node_modules/@sinonjs/commons": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.0.tgz", - "integrity": "sha512-wEj54PfsZ5jGSwMX68G8ZXFawcSglQSXqCftWX3ec8MDUzQdHgcKvw97awHbY0efQEL5iKUOAmmVtoYgmrSG4Q==", + "@babel/compat-data": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.15.0.tgz", + "integrity": "sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==", + "dev": true + }, + "@babel/core": { + "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.15.8.tgz", + "integrity": "sha512-3UG9dsxvYBMYwRv+gS41WKHno4K60/9GPy1CJaH6xy3Elq8CTtvtjT5R5jmNhXfCYLX2mTw+7/aq5ak/gOE0og==", "dev": true, - "dependencies": { - "type-detect": "4.0.8" + "requires": { + "@babel/code-frame": "^7.15.8", + "@babel/generator": "^7.15.8", + "@babel/helper-compilation-targets": "^7.15.4", + "@babel/helper-module-transforms": "^7.15.8", + "@babel/helpers": "^7.15.4", + "@babel/parser": "^7.15.8", + "@babel/template": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.6", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.1.2", + "semver": "^6.3.0", + "source-map": "^0.5.0" + } + }, + "@babel/generator": { + "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.8.tgz", + "integrity": "sha512-ECmAKstXbp1cvpTTZciZCgfOt6iN64lR0d+euv3UZisU5awfRawOvg07Utn/qBGuH4bRIEZKrA/4LzZyXhZr8g==", + "dev": true, + "requires": { + "@babel/types": "^7.15.6", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" } }, - "node_modules/@sinonjs/formatio": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.2.tgz", - "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", + "@babel/helper-compilation-targets": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.4.tgz", + "integrity": "sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ==", "dev": true, - "dependencies": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^3.1.0" + "requires": { + "@babel/compat-data": "^7.15.0", + "@babel/helper-validator-option": "^7.14.5", + "browserslist": "^4.16.6", + "semver": "^6.3.0" } }, - "node_modules/@sinonjs/samsam": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", - "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", + "@babel/helper-function-name": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", + "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.3.0", - "array-from": "^2.1.1", - "lodash": "^4.17.15" + "requires": { + "@babel/helper-get-function-arity": "^7.15.4", + "@babel/template": "^7.15.4", + "@babel/types": "^7.15.4" } }, - "node_modules/@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 - }, - "node_modules/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 - }, - "node_modules/balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "node_modules/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==", - "dependencies": { - "safe-buffer": "5.1.2" - }, - "engines": { - "node": ">= 0.8" + "@babel/helper-get-function-arity": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", + "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", + "dev": true, + "requires": { + "@babel/types": "^7.15.4" } }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "@babel/helper-hoist-variables": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz", + "integrity": "sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==", "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "requires": { + "@babel/types": "^7.15.4" } }, - "node_modules/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 - }, - "node_modules/cli": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz", - "integrity": "sha1-IoF1NPJL+klQw01TLUjsvGIbjBQ=", + "@babel/helper-member-expression-to-functions": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.4.tgz", + "integrity": "sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA==", "dev": true, - "dependencies": { - "exit": "0.1.2", - "glob": "^7.1.1" - }, - "engines": { - "node": ">=0.2.5" + "requires": { + "@babel/types": "^7.15.4" } }, - "node_modules/co-bluebird": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/co-bluebird/-/co-bluebird-1.1.0.tgz", - "integrity": "sha1-yLnzqTIKftMJh9zKGlw8/1llXHw=", - "dependencies": { - "bluebird": "^2.10.0", - "co-use": "^1.1.0" - }, - "engines": { - "node": ">=0.12.0" + "@babel/helper-module-imports": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz", + "integrity": "sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==", + "dev": true, + "requires": { + "@babel/types": "^7.15.4" } }, - "node_modules/co-bluebird/node_modules/bluebird": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", - "integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=" - }, - "node_modules/co-use": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/co-use/-/co-use-1.1.0.tgz", - "integrity": "sha1-xrs83xDLc17Kqdru2kbXJclKTmI=", - "engines": { - "node": ">=0.12.0" + "@babel/helper-module-transforms": { + "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.8.tgz", + "integrity": "sha512-DfAfA6PfpG8t4S6npwzLvTUpp0sS7JrcuaMiy1Y5645laRJIp/LiLGIBbQKaXSInK8tiGNI7FL7L8UvB8gdUZg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.15.4", + "@babel/helper-replace-supers": "^7.15.4", + "@babel/helper-simple-access": "^7.15.4", + "@babel/helper-split-export-declaration": "^7.15.4", + "@babel/helper-validator-identifier": "^7.15.7", + "@babel/template": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.6" } }, - "node_modules/commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true - }, - "node_modules/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 - }, - "node_modules/console-browserify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", - "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "@babel/helper-optimise-call-expression": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz", + "integrity": "sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw==", "dev": true, - "dependencies": { - "date-now": "^0.1.4" + "requires": { + "@babel/types": "^7.15.4" } }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "node_modules/date-now": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", - "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", - "dev": true - }, - "node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "@babel/helper-replace-supers": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.4.tgz", + "integrity": "sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw==", "dev": true, - "dependencies": { - "ms": "2.0.0" + "requires": { + "@babel/helper-member-expression-to-functions": "^7.15.4", + "@babel/helper-optimise-call-expression": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.4" } }, - "node_modules/diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "@babel/helper-simple-access": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.15.4.tgz", + "integrity": "sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg==", "dev": true, - "engines": { - "node": ">=0.3.1" + "requires": { + "@babel/types": "^7.15.4" } }, - "node_modules/dom-serializer": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", - "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "@babel/helper-split-export-declaration": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", + "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", "dev": true, - "dependencies": { - "domelementtype": "^2.0.1", - "entities": "^2.0.0" + "requires": { + "@babel/types": "^7.15.4" } }, - "node_modules/dom-serializer/node_modules/domelementtype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", - "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", - "dev": true - }, - "node_modules/dom-serializer/node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "@babel/helper-validator-identifier": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", "dev": true }, - "node_modules/domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "@babel/helper-validator-option": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", + "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", "dev": true }, - "node_modules/domhandler": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", - "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=", + "@babel/helpers": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.15.4.tgz", + "integrity": "sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ==", "dev": true, - "dependencies": { - "domelementtype": "1" + "requires": { + "@babel/template": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.4" } }, - "node_modules/domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, "dependencies": { - "dom-serializer": "0", - "domelementtype": "1" + "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" + } + }, + "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" + } + }, + "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 + }, + "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 + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "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" + } + } } }, - "node_modules/entities": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", - "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=", + "@babel/parser": { + "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.8.tgz", + "integrity": "sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA==", "dev": true }, - "node_modules/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=", + "@babel/template": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", + "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", "dev": true, - "engines": { - "node": ">=0.8.0" + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.15.4", + "@babel/types": "^7.15.4" } }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "@babel/traverse": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", + "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", "dev": true, - "engines": { - "node": ">= 0.8.0" + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.15.4", + "@babel/helper-function-name": "^7.15.4", + "@babel/helper-hoist-variables": "^7.15.4", + "@babel/helper-split-export-declaration": "^7.15.4", + "@babel/parser": "^7.15.4", + "@babel/types": "^7.15.4", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.15.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", + "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.9", + "to-fast-properties": "^2.0.0" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "@eslint/eslintrc": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.5.tgz", + "integrity": "sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ==", "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.2.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "strip-json-comments": "^3.1.1" }, - "engines": { - "node": "*" + "dependencies": { + "globals": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", + "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } } }, - "node_modules/growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "@humanwhocodes/config-array": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.2.tgz", + "integrity": "sha512-UXOuFCGcwciWckOpmfKDq/GyhlTf9pN/BzG//x8p8zTOFEcGuA68ANXheFS0AGvy3qgZqLBUkMs7hqzqCKOVwA==", "dev": true, - "engines": { - "node": ">=4.x" + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" } }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, - "engines": { - "node": ">=4" + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "dependencies": { + "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" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + } } }, - "node_modules/he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@node-oauth/formats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@node-oauth/formats/-/formats-1.0.0.tgz", + "integrity": "sha512-DwSbLtdC8zC5B5gTJkFzJj5s9vr9SGzOgQvV9nH7tUVuMSScg0EswAczhjIapOmH3Y8AyP7C4Jv7b8+QJObWZA==" + }, + "@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", "dev": true, - "bin": { - "he": "bin/he" + "requires": { + "type-detect": "4.0.8" } }, - "node_modules/htmlparser2": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", - "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", + "@sinonjs/fake-timers": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", + "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", "dev": true, - "dependencies": { - "domelementtype": "1", - "domhandler": "2.3", - "domutils": "1.5", - "entities": "1.0", - "readable-stream": "1.1" + "requires": { + "@sinonjs/commons": "^1.7.0" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "@sinonjs/samsam": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.0.2.tgz", + "integrity": "sha512-jxPRPp9n93ci7b8hMfJOFDPRLFYadN6FSpeROFTR4UNF4i5b+EK6m4QXPO46BDhFgRy1JuS87zAnFOzCUwMJcQ==", "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "requires": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "@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 }, - "node_modules/is-generator": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-generator/-/is-generator-1.0.3.tgz", - "integrity": "sha1-wUwhBX7TbjKNuANHlmxpP4hjifM=" + "@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true }, - "node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "acorn": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.6.0.tgz", + "integrity": "sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true }, - "node_modules/jshint": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.13.0.tgz", - "integrity": "sha512-Nd+md9wIeyfDK+RGrbOBzwLONSTdihGMtyGYU/t7zYcN2EgUa4iuY3VK2oxtPYrW5ycTj18iC+UbhNTxe4C66g==", + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", "dev": true, - "dependencies": { - "cli": "~1.0.0", - "console-browserify": "1.1.x", - "exit": "0.1.x", - "htmlparser2": "3.8.x", - "lodash": "~4.17.21", - "minimatch": "~3.0.2", - "shelljs": "0.3.x", - "strip-json-comments": "1.0.x" - }, - "bin": { - "jshint": "bin/jshint" + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" } }, - "node_modules/jshint/node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } }, - "node_modules/just-extend": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.0.tgz", - "integrity": "sha512-ApcjaOdVTJ7y4r08xI5wIqpvwS48Q0PBG4DJROcEkH1f8MdAiNFyFxz3xoL0LWAVwjrwPYZdVHHxhRHcx/uGLA==", + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", "dev": true }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/lolex": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.2.0.tgz", - "integrity": "sha512-gKO5uExCXvSm6zbF562EvM+rd1kQDnB9AZBbiQVzf1ZmdDpxUSvpnAaVOP83N/31mRK8Ml8/VE8DMvsAZQ+7wg==", + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", - "engines": { - "node": ">= 0.6" + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" } }, - "node_modules/mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", - "dependencies": { - "mime-db": "1.44.0" - }, - "engines": { - "node": ">= 0.6" + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" } }, - "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" + "requires": { + "default-require-extensions": "^3.0.0" } }, - "node_modules/minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", "dev": true }, - "node_modules/mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "dependencies": { - "minimist": "0.0.8" - }, - "bin": { - "mkdirp": "bin/cmd.js" + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "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" } }, - "node_modules/mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha1-bYrlCPWRZ/lA8rWzxKYSrlDJCuY=", + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "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, - "dependencies": { - "browser-stdout": "1.3.1", - "commander": "2.15.1", - "debug": "3.1.0", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.5", - "he": "1.1.1", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "supports-color": "5.4.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" - }, - "engines": { - "node": ">= 4.0.0" + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/mocha/node_modules/glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, - "dependencies": { - "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" - }, - "engines": { - "node": "*" + "requires": { + "fill-range": "^7.0.1" } }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "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 }, - "node_modules/nise": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.3.tgz", - "integrity": "sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==", + "browserslist": { + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.3.tgz", + "integrity": "sha512-59IqHJV5VGdcJZ+GZ2hU5n4Kv3YiASzW6Xk5g9tf5a/MAzGeFwgGWU39fVzNIOVcgB3+Gp+kiQu0HEfTVU/3VQ==", "dev": true, - "dependencies": { - "@sinonjs/formatio": "^3.2.1", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "lolex": "^5.0.1", - "path-to-regexp": "^1.7.0" + "requires": { + "caniuse-lite": "^1.0.30001264", + "electron-to-chromium": "^1.3.857", + "escalade": "^3.1.1", + "node-releases": "^1.1.77", + "picocolors": "^0.2.1" } }, - "node_modules/nise/node_modules/lolex": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", - "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", + "caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0" + "requires": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "dependencies": { - "wrappy": "1" - } + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true }, - "node_modules/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=", + "camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001265", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001265.tgz", + "integrity": "sha512-YzBnspggWV5hep1m9Z6sZVLOt7vrju8xWooFAgN6BA5qvy98qPAPb7vNUzypFaoh2pb3vlfzbDO8tB57UPGbtw==", + "dev": true + }, + "chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", "dev": true, - "engines": { - "node": ">=0.10.0" + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" } }, - "node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "dependencies": { - "isarray": "0.0.1" + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "node_modules/promisify-any": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promisify-any/-/promisify-any-2.0.1.tgz", - "integrity": "sha1-QD4AqIE/F1JCq1D+M6afjuzkcwU=", - "dependencies": { - "bluebird": "^2.10.0", - "co-bluebird": "^1.1.0", - "is-generator": "^1.0.2" + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "chokidar": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" }, - "engines": { - "node": ">=0.10.0" + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } } }, - "node_modules/promisify-any/node_modules/bluebird": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", - "integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=" + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true }, - "node_modules/readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "co-bluebird": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/co-bluebird/-/co-bluebird-1.1.0.tgz", + "integrity": "sha1-yLnzqTIKftMJh9zKGlw8/1llXHw=", + "requires": { + "bluebird": "^2.10.0", + "co-use": "^1.1.0" + }, "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "bluebird": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", + "integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=" + } } }, - "node_modules/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==" + "co-use": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/co-use/-/co-use-1.1.0.tgz", + "integrity": "sha1-xrs83xDLc17Kqdru2kbXJclKTmI=" }, - "node_modules/shelljs": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", - "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "bin": { - "shjs": "bin/shjs" - }, - "engines": { - "node": ">=0.8.0" + "requires": { + "color-name": "~1.1.4" } }, - "node_modules/should": { - "version": "13.2.3", - "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", - "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "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 + }, + "convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", "dev": true, - "dependencies": { - "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" + "requires": { + "safe-buffer": "~5.1.1" } }, - "node_modules/should-equal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", - "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, - "dependencies": { - "should-type": "^1.4.0" + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" } }, - "node_modules/should-format": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", - "integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=", + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", "dev": true, + "requires": { + "ms": "2.1.2" + }, "dependencies": { - "should-type": "^1.3.0", - "should-type-adaptors": "^1.0.1" + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, - "node_modules/should-type": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", - "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=", + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true }, - "node_modules/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==", + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", "dev": true, - "dependencies": { - "should-type": "^1.3.0", - "should-util": "^1.0.0" + "requires": { + "type-detect": "^4.0.0" } }, - "node_modules/should-util": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz", - "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==", + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, - "node_modules/sinon": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.5.0.tgz", - "integrity": "sha1-6UiOpGYHDqkI/USj1keP1JI8Z+w=", + "default-require-extensions": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", + "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.4.0", - "@sinonjs/formatio": "^3.2.1", - "@sinonjs/samsam": "^3.3.3", - "diff": "^3.5.0", - "lolex": "^4.2.0", - "nise": "^1.5.2", - "supports-color": "^5.5.0" + "requires": { + "strip-bom": "^4.0.0" } }, - "node_modules/sinon/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" + "requires": { + "esutils": "^2.0.2" } }, - "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "engines": { - "node": ">= 0.6" - } + "electron-to-chromium": { + "version": "1.3.867", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.867.tgz", + "integrity": "sha512-WbTXOv7hsLhjJyl7jBfDkioaY++iVVZomZ4dU6TMe/SzucV6mUAs2VZn/AehBwuZMiNEQDaPuTGn22YK5o+aDw==", + "dev": true }, - "node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "node_modules/strip-json-comments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", - "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", "dev": true, - "bin": { - "strip-json-comments": "cli.js" - }, - "engines": { - "node": ">=0.8.0" + "requires": { + "ansi-colors": "^4.1.1" } }, - "node_modules/supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.4.1.tgz", + "integrity": "sha512-TxU/p7LB1KxQ6+7aztTnO7K0i+h0tDi81YRY9VzB6Id71kNz+fFYnf5HD5UOQmxkzcoa0TlVZf9dpMtUv0GpWg==", "dev": true, - "dependencies": { - "has-flag": "^3.0.0" + "requires": { + "@eslint/eslintrc": "^1.0.5", + "@humanwhocodes/config-array": "^0.9.2", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.0", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.1.0", + "espree": "^9.2.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^6.0.1", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.2.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" }, - "engines": { - "node": ">=4" + "dependencies": { + "globals": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", + "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } } }, - "node_modules/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==", + "eslint-scope": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.0.tgz", + "integrity": "sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg==", "dev": true, - "engines": { - "node": ">=4" + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" } }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" }, - "engines": { - "node": ">= 0.6" + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "eslint-visitor-keys": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz", + "integrity": "sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA==", "dev": true - } - }, - "dependencies": { - "@sinonjs/commons": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.0.tgz", - "integrity": "sha512-wEj54PfsZ5jGSwMX68G8ZXFawcSglQSXqCftWX3ec8MDUzQdHgcKvw97awHbY0efQEL5iKUOAmmVtoYgmrSG4Q==", + }, + "espree": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.2.0.tgz", + "integrity": "sha512-oP3utRkynpZWF/F2x/HZJ+AGtnIclaR7z1pYPxy7NYM2fSO6LgK/Rkny8anRSPK/VwEA1eqm2squui0T7ZMOBg==", "dev": true, "requires": { - "type-detect": "4.0.8" + "acorn": "^8.6.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^3.1.0" } }, - "@sinonjs/formatio": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.2.tgz", - "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", + "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 + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", "dev": true, "requires": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^3.1.0" + "estraverse": "^5.1.0" } }, - "@sinonjs/samsam": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", - "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "requires": { - "@sinonjs/commons": "^1.3.0", - "array-from": "^2.1.1", - "lodash": "^4.17.15" + "estraverse": "^5.2.0" } }, - "@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==", + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "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=", + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "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==", + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, "requires": { - "safe-buffer": "5.1.2" + "flat-cache": "^3.0.4" } }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "to-regex-range": "^5.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==", + "find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz", + "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", "dev": true }, - "cli": { + "foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + } + }, + "fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "functional-red-black-tree": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz", - "integrity": "sha1-IoF1NPJL+klQw01TLUjsvGIbjBQ=", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "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-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "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" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "requires": { - "exit": "0.1.2", - "glob": "^7.1.1" + "is-glob": "^4.0.3" } }, - "co-bluebird": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/co-bluebird/-/co-bluebird-1.1.0.tgz", - "integrity": "sha1-yLnzqTIKftMJh9zKGlw8/1llXHw=", + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "graceful-fs": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "dev": true + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, "requires": { - "bluebird": "^2.10.0", - "co-use": "^1.1.0" + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + } + }, + "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 + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "dependencies": { - "bluebird": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", - "integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=" + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true } } }, - "co-use": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/co-use/-/co-use-1.1.0.tgz", - "integrity": "sha1-xrs83xDLc17Kqdru2kbXJclKTmI=" + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true }, - "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true }, - "concat-map": { + "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.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-generator": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-generator/-/is-generator-1.0.3.tgz", + "integrity": "sha1-wUwhBX7TbjKNuANHlmxpP4hjifM=" + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "isarray": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", "dev": true }, - "console-browserify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", - "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.2.tgz", + "integrity": "sha512-o5+eTUYzCJ11/+JhW5/FUCdfsdoYVdQ/8I/OveE2XsjehYn5DdeSnNQAbjYaO8gQ6hvGTN6GM6ddQqpTVG5j8g==", + "dev": true + }, + "istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "requires": { + "append-transform": "^2.0.0" + } + }, + "istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "requires": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + } + }, + "istanbul-lib-processinfo": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz", + "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==", + "dev": true, + "requires": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.0", + "istanbul-lib-coverage": "^3.0.0-alpha.1", + "make-dir": "^3.0.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^3.3.3" + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", "dev": true, "requires": { - "date-now": "^0.1.4" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "dependencies": { + "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 + } + } + }, + "istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-bFjUnc95rHjdCR63WMHUS7yfJJh8T9IPSWavvR02hhjVwezWALZ5axF9EqjmwZHpXqkzbgAMP8DmAtiyNxrdrQ==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "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": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "json5": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "dev": true + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.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=" + }, + "mime-db": { + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.50.0.tgz", + "integrity": "sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==" + }, + "mime-types": { + "version": "2.1.33", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.33.tgz", + "integrity": "sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==", + "requires": { + "mime-db": "1.50.0" + } + }, + "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": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mocha": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.1.2.tgz", + "integrity": "sha512-ta3LtJ+63RIBP03VBjMGtSqbe6cWXRejF9SyM9Zyli1CKZJZ+vfCTj3oW24V7wAphMJdpOFLoMI3hjJ1LWbs0w==", + "dev": true, + "requires": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.2", + "debug": "4.3.2", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.7", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "3.0.4", + "ms": "2.1.3", + "nanoid": "3.1.25", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "workerpool": "6.1.5", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "dependencies": { + "glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "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" + } + } + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "nanoid": { + "version": "3.1.25", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz", + "integrity": "sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "nise": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.0.tgz", + "integrity": "sha512-W5WlHu+wvo3PaKLsJJkgPup2LrsXCcm7AWwyNZkUnn5rwPkuPBi3Iwk5SQtN0mv+K65k7nKKjwNQ30wg3wLAQQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0", + "@sinonjs/fake-timers": "^7.0.4", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + }, + "dependencies": { + "@sinonjs/fake-timers": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", + "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + } + } + }, + "node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "requires": { + "process-on-spawn": "^1.0.0" + } + }, + "node-releases": { + "version": "1.1.77", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.77.tgz", + "integrity": "sha512-rB1DUFUNAN4Gn9keO2K1efO35IDK7yKHCdCaIMvFO7yUYmmZYeDjnGKle26G4rwj+LKRQpjyUUvMkPglwGCYNQ==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "requires": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.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 + }, + "package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "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": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-to-regexp": { + "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" + } + }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true + }, + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + } } }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "date-now": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", - "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", "dev": true, "requires": { - "ms": "2.0.0" + "fromentries": "^1.2.0" } }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, - "dom-serializer": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", - "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", - "dev": true, + "promisify-any": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promisify-any/-/promisify-any-2.0.1.tgz", + "integrity": "sha1-QD4AqIE/F1JCq1D+M6afjuzkcwU=", "requires": { - "domelementtype": "^2.0.1", - "entities": "^2.0.0" + "bluebird": "^2.10.0", + "co-bluebird": "^1.1.0", + "is-generator": "^1.0.2" }, "dependencies": { - "domelementtype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", - "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", - "dev": true - }, - "entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true + "bluebird": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", + "integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=" } } }, - "domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, - "domhandler": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", - "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=", + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, "requires": { - "domelementtype": "1" + "safe-buffer": "^5.1.0" } }, - "domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, "requires": { - "dom-serializer": "0", - "domelementtype": "1" + "picomatch": "^2.2.1" } }, - "entities": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", - "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=", + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true }, - "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=", + "release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "dev": true, + "requires": { + "es6-error": "^4.0.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 }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "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 }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true }, - "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "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" + "glob": "^7.1.3" } }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "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==" }, - "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, - "htmlparser2": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", - "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", + "serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", "dev": true, "requires": { - "domelementtype": "1", - "domhandler": "2.3", - "domutils": "1.5", - "entities": "1.0", - "readable-stream": "1.1" + "randombytes": "^2.1.0" } }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "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": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { - "once": "^1.3.0", - "wrappy": "1" + "shebang-regex": "^3.0.0" } }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, - "is-generator": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-generator/-/is-generator-1.0.3.tgz", - "integrity": "sha1-wUwhBX7TbjKNuANHlmxpP4hjifM=" - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "signal-exit": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz", + "integrity": "sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==", "dev": true }, - "jshint": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.13.0.tgz", - "integrity": "sha512-Nd+md9wIeyfDK+RGrbOBzwLONSTdihGMtyGYU/t7zYcN2EgUa4iuY3VK2oxtPYrW5ycTj18iC+UbhNTxe4C66g==", + "sinon": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-12.0.1.tgz", + "integrity": "sha512-iGu29Xhym33ydkAT+aNQFBINakjq69kKO6ByPvTsm3yyIACfyQttRTP03aBP/I8GfhFmLzrnKwNNkr0ORb1udg==", "dev": true, "requires": { - "cli": "~1.0.0", - "console-browserify": "1.1.x", - "exit": "0.1.x", - "htmlparser2": "3.8.x", - "lodash": "~4.17.21", - "minimatch": "~3.0.2", - "shelljs": "0.3.x", - "strip-json-comments": "1.0.x" + "@sinonjs/commons": "^1.8.3", + "@sinonjs/fake-timers": "^8.1.0", + "@sinonjs/samsam": "^6.0.2", + "diff": "^5.0.0", + "nise": "^5.1.0", + "supports-color": "^7.2.0" }, "dependencies": { - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } } } }, - "just-extend": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.0.tgz", - "integrity": "sha512-ApcjaOdVTJ7y4r08xI5wIqpvwS48Q0PBG4DJROcEkH1f8MdAiNFyFxz3xoL0LWAVwjrwPYZdVHHxhRHcx/uGLA==", + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "requires": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + } }, - "lolex": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.2.0.tgz", - "integrity": "sha512-gKO5uExCXvSm6zbF562EvM+rd1kQDnB9AZBbiQVzf1ZmdDpxUSvpnAaVOP83N/31mRK8Ml8/VE8DMvsAZQ+7wg==", + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" - }, - "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" - }, - "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "requires": { - "mime-db": "1.44.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" } }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "brace-expansion": "^1.1.7" + "ansi-regex": "^5.0.1" } }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "requires": { - "minimist": "0.0.8" + "has-flag": "^4.0.0" } }, - "mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha1-bYrlCPWRZ/lA8rWzxKYSrlDJCuY=", + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, "requires": { - "browser-stdout": "1.3.1", - "commander": "2.15.1", - "debug": "3.1.0", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.5", - "he": "1.1.1", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "supports-color": "5.4.0" - }, - "dependencies": { - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", - "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" - } - } + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" } }, - "ms": { + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "to-fast-properties": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", "dev": true }, - "nise": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.3.tgz", - "integrity": "sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==", + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "requires": { - "@sinonjs/formatio": "^3.2.1", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "lolex": "^5.0.1", - "path-to-regexp": "^1.7.0" - }, - "dependencies": { - "lolex": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", - "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - } + "is-number": "^7.0.0" } }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "requires": { - "wrappy": "1" + "prelude-ls": "^1.2.1" } }, - "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=", + "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 }, - "path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dev": true, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "requires": { - "isarray": "0.0.1" + "media-typer": "0.3.0", + "mime-types": "~2.1.24" } }, - "promisify-any": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promisify-any/-/promisify-any-2.0.1.tgz", - "integrity": "sha1-QD4AqIE/F1JCq1D+M6afjuzkcwU=", + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, "requires": { - "bluebird": "^2.10.0", - "co-bluebird": "^1.1.0", - "is-generator": "^1.0.2" - }, - "dependencies": { - "bluebird": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", - "integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=" - } + "is-typedarray": "^1.0.0" } }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "punycode": "^2.1.0" } }, - "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==" + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true }, - "shelljs": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", - "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=", + "v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "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==", + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "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" + "isexe": "^2.0.0" } }, - "should-equal": { + "which-module": { "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" - } + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true }, - "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" - } + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true }, - "should-type": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", - "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=", + "workerpool": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.5.tgz", + "integrity": "sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw==", "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==", + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "requires": { - "should-type": "^1.3.0", - "should-util": "^1.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" } }, - "should-util": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz", - "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==", + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, - "sinon": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.5.0.tgz", - "integrity": "sha1-6UiOpGYHDqkI/USj1keP1JI8Z+w=", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.4.0", - "@sinonjs/formatio": "^3.2.1", - "@sinonjs/samsam": "^3.3.3", - "diff": "^3.5.0", - "lolex": "^4.2.0", - "nise": "^1.5.2", - "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": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" } }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true }, - "strip-json-comments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", - "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "requires": { - "has-flag": "^3.0.0" - } - }, - "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==", + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", "dev": true }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" } }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true } } diff --git a/package.json b/package.json index e4d04f08b..7e6000c63 100644 --- a/package.json +++ b/package.json @@ -1,65 +1,66 @@ { - "name": "oauth2-server", + "name": "@node-oauth/oauth2-server", "description": "Complete, framework-agnostic, compliant and well tested module for implementing an OAuth2 Server in node.js", - "version": "4.0.0-dev.3", + "version": "4.1.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" - } + "Thom Seddon ", + "Lars F. Karlström ", + "Rui Marinho ", + "Tiago Ribeiro ", + "Michael Salinger ", + "Nuno Sousa", + "Max Truxa", + "Daniel Reguero" ], "main": "index.js", + "types": "index.d.ts", + "files": [ + "index.js", + "index.d.ts", + "lib" + ], "dependencies": { + "@node-oauth/formats": "^1.0.0", "basic-auth": "2.0.1", "bluebird": "3.7.2", - "lodash": "4.17.21", "promisify-any": "2.0.1", - "statuses": "1.5.0", "type-is": "1.6.18" }, "devDependencies": { - "jshint": "2.13.0", - "mocha": "5.2.0", - "should": "13.2.3", - "sinon": "7.5.0" + "chai": "^4.3.4", + "eslint": "^8.0.0", + "mocha": "^9.1.2", + "nyc": "^15.1.0", + "sinon": "^12.0.1" }, "license": "MIT", "engines": { - "node": ">=4.0" + "node": ">=12.0.0" }, "scripts": { - "pretest": "./node_modules/.bin/jshint --config ./.jshintrc lib test", + "pretest": "./node_modules/.bin/eslint lib test index.js", "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'" + "test-debug": "NODE_ENV=test ./node_modules/.bin/mocha --inspect --debug-brk 'test/**/*_test.js'", + "test:watch": "NODE_ENV=test ./node_modules/.bin/mocha --watch 'test/**/*_test.js'", + "test:coverage": "NODE_ENV=test nyc --reporter=html --reporter=lcov --reporter=text ./node_modules/.bin/mocha 'test/**/*_test.js'", + "lint": "npx eslint .", + "lint:fix": "npx eslint . --fix" }, "repository": { "type": "git", - "url": "https://github.com/oauthjs/node-oauth2-server.git" + "url": "git+https://github.com/node-oauth/node-oauth2-server.git" + }, + "bugs": { + "url": "https://github.com/node-oauth/node-oauth2-server/issues" + }, + "homepage": "https://github.com/node-oauth/node-oauth2-server#readme", + "directories": { + "doc": "docs", + "lib": "lib", + "test": "test" } } diff --git a/test/assertions.js b/test/assertions.js index 6846e3cdf..7a95c809d 100644 --- a/test/assertions.js +++ b/test/assertions.js @@ -4,14 +4,15 @@ * Module dependencies. */ -var should = require('should'); +const chai = require('chai'); /** - * SHA-1 assertion. + * SHA-256 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); +chai.use(function (_chai, utils) { + chai.Assertion.addMethod('sha256', function (...args) { + const obj = utils.flag(this, 'object'); + new chai.Assertion(obj).match(/^[a-f0-9]{64}$/i); + }); +}); diff --git a/test/compliance/client-authentication_test.js b/test/compliance/client-authentication_test.js new file mode 100644 index 000000000..72624ec5b --- /dev/null +++ b/test/compliance/client-authentication_test.js @@ -0,0 +1,128 @@ +/** + * @see https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1 + * + * For example (with extra line breaks for display purposes only): + * + * Authorization: Basic czZCaGRSa3F0Mzo3RmpmcDBaQnIxS3REUmJuZlZkbUl3 + * + * Alternatively, the authorization server MAY support including the + * client credentials in the request-body using the following + * parameters: + * + * client_id + * REQUIRED. The client identifier issued to the client during + * the registration process described by Section 2.2. + * + * client_secret + * REQUIRED. The client secret. The client MAY omit the + * parameter if the client secret is an empty string. + */ + +const OAuth2Server = require('../..'); +const DB = require('../helpers/db'); +const createModel = require('../helpers/model'); +const createRequest = require('../helpers/request'); +const Response = require('../../lib/response'); + +require('chai').should(); + +const db = new DB(); + +const auth = new OAuth2Server({ + model: createModel(db) +}); + +const user = db.saveUser({ id: 1, username: 'test', password: 'test'}); +const client = db.saveClient({ id: 'a', secret: 'b', grants: ['password'] }); +const scope = 'read write'; + +function createDefaultRequest () { + return createRequest({ + body: { + grant_type: 'password', + username: user.username, + password: user.password, + scope + }, + headers: { + 'authorization': 'Basic ' + Buffer.from(client.id + ':' + client.secret).toString('base64'), + 'content-type': 'application/x-www-form-urlencoded' + }, + method: 'POST', + }); +} + +describe('Client Authentication Compliance', function () { + describe('No authentication', function () { + it('should be an unsuccesfull authentication', async function () { + const request = createDefaultRequest(); + const response = new Response({}); + + delete request.headers.authorization; + + await auth.token(request, response, {}) + .then((token) => { + throw new Error('Should not be here'); + }). + catch(err => { + err.name.should.equal('invalid_client'); + }); + }); + }); + + describe('Basic Authentication', function () { + it('should be a succesfull authentication', async function () { + const request = createDefaultRequest(); + const response = new Response({}); + + await auth.token(request, response, {}); + }); + + it('should be an unsuccesfull authentication', async function () { + const request = createDefaultRequest(); + const response = new Response({}); + + request.headers.authorization = 'Basic ' + Buffer.from('a:c').toString('base64'); + + await auth.token(request, response, {}) + .then((token) => { + throw new Error('Should not be here'); + }). + catch(err => { + err.name.should.equal('invalid_client'); + }); + }); + }); + + describe('Request body authentication', function () { + it('should be a succesfull authentication', async function () { + const request = createDefaultRequest(); + const response = new Response({}); + + delete request.headers.authorization; + + request.body.client_id = client.id; + request.body.client_secret = client.secret; + + await auth.token(request, response, {}); + }); + + it('should be an unsuccesfull authentication', async function () { + const request = createDefaultRequest(); + const response = new Response({}); + + delete request.headers.authorization; + + request.body.client_id = 'a'; + request.body.client_secret = 'c'; + + await auth.token(request, response, {}) + .then((token) => { + throw new Error('Should not be here'); + }) + .catch(err => { + err.name.should.equal('invalid_client'); + }); + }); + }); +}); diff --git a/test/compliance/password-grant-type_test.js b/test/compliance/password-grant-type_test.js new file mode 100644 index 000000000..7941d54fe --- /dev/null +++ b/test/compliance/password-grant-type_test.js @@ -0,0 +1,236 @@ +/** + * Request + * @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.3.2 + * + * grant_type + * REQUIRED. Value MUST be set to "password". + * username + * REQUIRED. The resource owner username. + * password + * REQUIRED. The resource owner password. + * scope + * OPTIONAL. The scope of the access request as described by Section 3.3. + */ + +/** + * Response + * @see https://datatracker.ietf.org/doc/html/rfc6749#section-5.1 + * + * access_token + * REQUIRED. The access token issued by the authorization server. + * token_type + * REQUIRED. The type of the token issued as described in + * Section 7.1. Value is case insensitive. + * expires_in + * RECOMMENDED. The lifetime in seconds of the access token. For + * example, the value "3600" denotes that the access token will + * expire in one hour from the time the response was generated. + * If omitted, the authorization server SHOULD provide the + * expiration time via other means or document the default value. + * refresh_token + * OPTIONAL. The refresh token, which can be used to obtain new + * access tokens using the same authorization grant as described + * in Section 6. + * scope + * OPTIONAL, if identical to the scope requested by the client; + * otherwise, REQUIRED. The scope of the access token as + * described by Section 3.3. + */ + +/** + * Response (error) + * @see https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 + * + * error + * REQUIRED. A single ASCII [USASCII] error code from the following: + * invalid_request, invalid_client, invalid_grant + * unauthorized_client, unsupported_grant_type, invalid_scope + * error_description + * OPTIONAL. Human-readable ASCII [USASCII] text providing + * additional information, used to assist the client developer in + * understanding the error that occurred. + * error_uri + * OPTIONAL. A URI identifying a human-readable web page with + * information about the error, used to provide the client + * developer with additional information about the error. + */ + +const OAuth2Server = require('../..'); +const DB = require('../helpers/db'); +const createModel = require('../helpers/model'); +const createRequest = require('../helpers/request'); +const Response = require('../../lib/response'); +const crypto = require('crypto'); + +require('chai').should(); + +const db = new DB(); + +const auth = new OAuth2Server({ + model: createModel(db) +}); + +const user = db.saveUser({ id: 1, username: 'test', password: 'test'}); +const client = db.saveClient({ id: 'a', secret: 'b', grants: ['password'] }); +const scope = 'read write'; + +function createDefaultRequest () { + return createRequest({ + body: { + grant_type: 'password', + username: user.username, + password: user.password, + scope + }, + headers: { + 'authorization': 'Basic ' + Buffer.from(client.id + ':' + client.secret).toString('base64'), + 'content-type': 'application/x-www-form-urlencoded' + }, + method: 'POST', + }); +} + +describe('PasswordGrantType Compliance', function () { + describe('Authenticate', function () { + it ('Succesfull authorization', async function () { + const request = createDefaultRequest(); + const response = new Response({}); + + const token = await auth.token(request, response, {}); + response.body.token_type.should.equal('Bearer'); + response.body.access_token.should.equal(token.accessToken); + response.body.refresh_token.should.equal(token.refreshToken); + response.body.expires_in.should.be.a('number'); + response.body.scope.should.equal(scope); + + token.accessToken.should.be.a('string'); + token.refreshToken.should.be.a('string'); + token.accessTokenExpiresAt.should.be.a('date'); + token.refreshTokenExpiresAt.should.be.a('date'); + token.scope.should.equal(scope); + + db.accessTokens.has(token.accessToken).should.equal(true); + db.refreshTokens.has(token.refreshToken).should.equal(true); + }); + + it ('Succesfull authorization and authentication', async function () { + const tokenRequest = createDefaultRequest(); + const tokenResponse = new Response({}); + + const token = await auth.token(tokenRequest, tokenResponse, {}); + + const authenticationRequest = createRequest({ + body: {}, + headers: { + 'Authorization': `Bearer ${token.accessToken}` + }, + method: 'GET', + query: {} + }); + const authenticationResponse = new Response({}); + + const authenticated = await auth.authenticate( + authenticationRequest, + authenticationResponse, + {}); + + authenticated.scope.should.equal(scope); + authenticated.user.should.be.an('object'); + authenticated.client.should.be.an('object'); + }); + + it ('Username missing', async function () { + const request = createDefaultRequest(); + const response = new Response({}); + + delete request.body.username; + + await auth.token(request, response, {}) + .catch(err => { + err.name.should.equal('invalid_request'); + }); + }); + + it ('Password missing', async function () { + const request = createDefaultRequest(); + const response = new Response({}); + + delete request.body.password; + + await auth.token(request, response, {}) + .catch(err => { + err.name.should.equal('invalid_request'); + }); + }); + + it ('Wrong username', async function () { + const request = createDefaultRequest(); + const response = new Response({}); + + request.body.username = 'wrong'; + + await auth.token(request, response, {}) + .catch(err => { + err.name.should.equal('invalid_grant'); + }); + }); + + it ('Wrong password', async function () { + const request = createDefaultRequest(); + const response = new Response({}); + + request.body.password = 'wrong'; + + await auth.token(request, response, {}) + .catch(err => { + err.name.should.equal('invalid_grant'); + }); + }); + + it ('Client not found', async function () { + const request = createDefaultRequest(); + const response = new Response({}); + + const clientId = crypto.randomBytes(4).toString('hex'); + const clientSecret = crypto.randomBytes(4).toString('hex'); + + request.headers.authorization = 'Basic ' + Buffer.from(`${clientId}:${clientSecret}`).toString('base64'); + + await auth.token(request, response, {}) + .catch(err => { + err.name.should.equal('invalid_client'); + }); + }); + + it ('Client secret not required', async function () { + const request = createDefaultRequest(); + const response = new Response({}); + + delete request.body.client_secret; + + const token = await auth.token(request, response, { + requireClientAuthentication: { + password: false + } + }); + + token.accessToken.should.be.a('string'); + }); + + it ('Client secret required', async function () { + const request = createDefaultRequest(); + const response = new Response({}); + + delete request.body.client_secret; + + await auth.token(request, response, { + requireClientAuthentication: { + password: false + } + }) + .catch(err => { + err.name.should.equal('invalid_client'); + }); + }); + }); +}); diff --git a/test/compliance/refresh-token-grant-type_test.js b/test/compliance/refresh-token-grant-type_test.js new file mode 100644 index 000000000..b01fef3db --- /dev/null +++ b/test/compliance/refresh-token-grant-type_test.js @@ -0,0 +1,173 @@ +/** + * Request an access token using the refresh token grant type. + * @see https://datatracker.ietf.org/doc/html/rfc6749#section-6 + * + * grant_type + * REQUIRED. Value MUST be set to "refresh_token". + * refresh_token + * REQUIRED. The refresh token issued to the client. + * scope + * OPTIONAL. The scope of the access request as described by + * Section 3.3. The requested scope MUST NOT include any scope + * not originally granted by the resource owner, and if omitted is + * treated as equal to the scope originally granted by the + * resource owner. + */ + + +/** + * Response + * @see https://datatracker.ietf.org/doc/html/rfc6749#section-5.1 + * + * access_token + * REQUIRED. The access token issued by the authorization server. + * token_type + * REQUIRED. The type of the token issued as described in + * Section 7.1. Value is case insensitive. + * expires_in + * RECOMMENDED. The lifetime in seconds of the access token. For + * example, the value "3600" denotes that the access token will + * expire in one hour from the time the response was generated. + * If omitted, the authorization server SHOULD provide the + * expiration time via other means or document the default value. + * refresh_token + * OPTIONAL. The refresh token, which can be used to obtain new + * access tokens using the same authorization grant as described + * in Section 6. + * scope + * OPTIONAL, if identical to the scope requested by the client; + * otherwise, REQUIRED. The scope of the access token as + * described by Section 3.3. + */ + +/** + * Response (error) + * @see https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 + * + * error + * REQUIRED. A single ASCII [USASCII] error code from the following: + * invalid_request, invalid_client, invalid_grant + * unauthorized_client, unsupported_grant_type, invalid_scope + * error_description + * OPTIONAL. Human-readable ASCII [USASCII] text providing + * additional information, used to assist the client developer in + * understanding the error that occurred. + * error_uri + * OPTIONAL. A URI identifying a human-readable web page with + * information about the error, used to provide the client + * developer with additional information about the error. + */ +const OAuth2Server = require('../..'); +const DB = require('../helpers/db'); +const createModel = require('../helpers/model'); +const createRequest = require('../helpers/request'); +const Response = require('../../lib/response'); + +require('chai').should(); + +const db = new DB(); + +const auth = new OAuth2Server({ + model: createModel(db) +}); + +const user = db.saveUser({ id: 1, username: 'test', password: 'test'}); +const client = db.saveClient({ id: 'a', secret: 'b', grants: ['password', 'refresh_token'] }); +const scope = 'read write'; + +function createLoginRequest () { + return createRequest({ + body: { + grant_type: 'password', + username: user.username, + password: user.password, + scope + }, + headers: { + 'authorization': 'Basic ' + Buffer.from(client.id + ':' + client.secret).toString('base64'), + 'content-type': 'application/x-www-form-urlencoded' + }, + method: 'POST', + }); +} + +function createRefreshRequest (refresh_token) { + return createRequest({ + method: 'POST', + body: { + grant_type: 'refresh_token', + refresh_token, + scope + }, + headers: { + 'authorization': 'Basic ' + Buffer.from(client.id + ':' + client.secret).toString('base64'), + 'content-type': 'application/x-www-form-urlencoded' + } + }); +} + +describe('RefreshTokenGrantType Compliance', function () { + describe('With scope', function () { + it('Should generate token response', async function () { + const request = createLoginRequest(); + const response = new Response({}); + + const credentials = await auth.token(request, response, {}); + + const refreshRequest = createRefreshRequest(credentials.refreshToken); + const refreshResponse = new Response({}); + + const token = await auth.token(refreshRequest, refreshResponse, {}); + + refreshResponse.body.token_type.should.equal('Bearer'); + refreshResponse.body.access_token.should.equal(token.accessToken); + refreshResponse.body.refresh_token.should.equal(token.refreshToken); + refreshResponse.body.expires_in.should.be.a('number'); + refreshResponse.body.scope.should.equal(scope); + + token.accessToken.should.be.a('string'); + token.refreshToken.should.be.a('string'); + token.accessTokenExpiresAt.should.be.a('date'); + token.refreshTokenExpiresAt.should.be.a('date'); + token.scope.should.equal(scope); + + db.accessTokens.has(token.accessToken).should.equal(true); + db.refreshTokens.has(token.refreshToken).should.equal(true); + }); + + it('Should throw invalid_grant error', async function () { + const request = createRefreshRequest('invalid'); + const response = new Response({}); + + await auth.token(request, response, {}) + .then(() => { + throw Error('Should not reach this'); + }).catch(err => { + err.name.should.equal('invalid_grant'); + }); + }); + + // TODO: test refresh token with different scopes + // https://github.com/node-oauth/node-oauth2-server/issues/104 + + // it('Should throw invalid_scope error', async function () { + // const request = createLoginRequest(); + // const response = new Response({}); + + // const credentials = await auth.token(request, response, {}); + + // const refreshRequest = createRefreshRequest(credentials.refreshToken); + // const refreshResponse = new Response({}); + + // refreshRequest.scope = 'invalid'; + + // await auth.token(refreshRequest, refreshResponse, {}) + // .then(() => { + // throw Error('Should not reach this'); + // }) + // .catch(err => { + // err.name.should.equal('invalid_scope'); + // }); + // }); + }); +}); diff --git a/test/helpers/db.js b/test/helpers/db.js new file mode 100644 index 000000000..147d17410 --- /dev/null +++ b/test/helpers/db.js @@ -0,0 +1,70 @@ +class DB { + constructor () { + this.users = new Map(); + this.clients = []; + this.accessTokens = new Map(); + this.refreshTokens= new Map(); + } + + saveUser (user) { + this.users.set(user.id, user); + + return user; + } + + findUser (username, password) { + return Array.from(this.users.values()).find(user => { + return user.username === username && user.password === password; + }); + } + + findUserById (id) { + return this.users.get(id); + } + + saveClient (client) { + this.clients.push(client); + + return client; + } + + findClient (clientId, clientSecret) { + return this.clients.find(client => { + if (clientSecret) { + return client.id === clientId && client.secret === clientSecret; + } else { + return client.id === clientId; + } + }); + } + + findClientById (id) { + return this.clients.find(client => client.id === id); + } + + saveAccessToken (accessToken, meta) { + this.accessTokens.set(accessToken, meta); + } + + findAccessToken (accessToken) { + return this.accessTokens.get(accessToken); + } + + deleteAccessToken (accessToken) { + this.accessTokens.delete(accessToken); + } + + saveRefreshToken (refreshToken, meta) { + this.refreshTokens.set(refreshToken, meta); + } + + findRefreshToken (refreshToken) { + return this.refreshTokens.get(refreshToken); + } + + deleteRefreshToken (refreshToken) { + this.refreshTokens.delete(refreshToken); + } +} + +module.exports = DB; diff --git a/test/helpers/model.js b/test/helpers/model.js new file mode 100644 index 000000000..7a1893b1d --- /dev/null +++ b/test/helpers/model.js @@ -0,0 +1,92 @@ +const scopes = ['read', 'write']; + +function createModel (db) { + async function getUser (username, password) { + return db.findUser(username, password); + } + + async function getClient (clientId, clientSecret) { + return db.findClient(clientId, clientSecret); + } + + async function saveToken (token, client, user) { + const meta = { + clientId: client.id, + userId: user.id, + scope: token.scope, + accessTokenExpiresAt: token.accessTokenExpiresAt, + refreshTokenExpiresAt: token.refreshTokenExpiresAt + }; + + token.client = client; + token.user = user; + + if (token.accessToken) { + db.saveAccessToken(token.accessToken, meta); + } + + if (token.refreshToken) { + db.saveRefreshToken(token.refreshToken, meta); + } + + return token; + } + + async function getAccessToken (accessToken) { + const meta = db.findAccessToken(accessToken); + + if (!meta) { + return false; + } + + return { + accessToken, + accessTokenExpiresAt: meta.accessTokenExpiresAt, + user: db.findUserById(meta.userId), + client: db.findClientById(meta.clientId), + scope: meta.scope + }; + } + + async function getRefreshToken (refreshToken) { + const meta = db.findRefreshToken(refreshToken); + + if (!meta) { + return false; + } + + return { + refreshToken, + refreshTokenExpiresAt: meta.refreshTokenExpiresAt, + user: db.findUserById(meta.userId), + client: db.findClientById(meta.clientId), + scope: meta.scope + }; + } + + async function revokeToken (token) { + db.deleteRefreshToken(token.refreshToken); + + return true; + } + + async function verifyScope (token, scope) { + if (typeof scope === 'string') { + return scopes.includes(scope); + } else { + return scope.every(s => scopes.includes(s)); + } + } + + return { + getUser, + getClient, + saveToken, + getAccessToken, + getRefreshToken, + revokeToken, + verifyScope + }; +} + +module.exports = createModel; diff --git a/test/helpers/request.js b/test/helpers/request.js new file mode 100644 index 000000000..be556a8d5 --- /dev/null +++ b/test/helpers/request.js @@ -0,0 +1,17 @@ +const Request = require('../../lib/request'); + +module.exports = (request) => { + const req = new Request({ + query: {}, + body: {}, + headers: {}, + method: 'GET', + ...request + }); + + req.is = function (header) { + return this.headers['content-type'] === header; + }; + + return req; +}; diff --git a/test/integration/grant-types/abstract-grant-type_test.js b/test/integration/grant-types/abstract-grant-type_test.js index 6da489cd9..a6c4d2be9 100644 --- a/test/integration/grant-types/abstract-grant-type_test.js +++ b/test/integration/grant-types/abstract-grant-type_test.js @@ -4,11 +4,11 @@ * 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'); +const AbstractGrantType = require('../../../lib/grant-types/abstract-grant-type'); +const InvalidArgumentError = require('../../../lib/errors/invalid-argument-error'); +const Promise = require('bluebird'); +const Request = require('../../../lib/request'); +const should = require('chai').should(); /** * Test `AbstractGrantType` integration. @@ -39,20 +39,20 @@ describe('AbstractGrantType integration', function() { }); it('should set the `accessTokenLifetime`', function() { - var grantType = new AbstractGrantType({ accessTokenLifetime: 123, model: {} }); + const 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 }); + const model = {}; + const 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 }); + const grantType = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); grantType.refreshTokenLifetime.should.equal(456); }); @@ -60,33 +60,33 @@ describe('AbstractGrantType integration', function() { describe('generateAccessToken()', function() { it('should return an access token', function() { - var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); + const handler = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); return handler.generateAccessToken() .then(function(data) { - data.should.be.a.sha1; + data.should.be.a.sha256(); }) .catch(should.fail); }); it('should support promises', function() { - var model = { + const model = { generateAccessToken: function() { return Promise.resolve({}); } }; - var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: model, refreshTokenLifetime: 456 }); + const handler = new AbstractGrantType({ accessTokenLifetime: 123, model: model, refreshTokenLifetime: 456 }); handler.generateAccessToken().should.be.an.instanceOf(Promise); }); it('should support non-promises', function() { - var model = { + const model = { generateAccessToken: function() { return {}; } }; - var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: model, refreshTokenLifetime: 456 }); + const handler = new AbstractGrantType({ accessTokenLifetime: 123, model: model, refreshTokenLifetime: 456 }); handler.generateAccessToken().should.be.an.instanceOf(Promise); }); @@ -94,33 +94,33 @@ describe('AbstractGrantType integration', function() { describe('generateRefreshToken()', function() { it('should return a refresh token', function() { - var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); + const handler = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); return handler.generateRefreshToken() .then(function(data) { - data.should.be.a.sha1; + data.should.be.a.sha256(); }) .catch(should.fail); }); it('should support promises', function() { - var model = { + const model = { generateRefreshToken: function() { return Promise.resolve({}); } }; - var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: model, refreshTokenLifetime: 456 }); + const handler = new AbstractGrantType({ accessTokenLifetime: 123, model: model, refreshTokenLifetime: 456 }); handler.generateRefreshToken().should.be.an.instanceOf(Promise); }); it('should support non-promises', function() { - var model = { + const model = { generateRefreshToken: function() { return {}; } }; - var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: model, refreshTokenLifetime: 456 }); + const handler = new AbstractGrantType({ accessTokenLifetime: 123, model: model, refreshTokenLifetime: 456 }); handler.generateRefreshToken().should.be.an.instanceOf(Promise); }); @@ -128,7 +128,7 @@ describe('AbstractGrantType integration', function() { describe('getAccessTokenExpiresAt()', function() { it('should return a date', function() { - var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); + const handler = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); handler.getAccessTokenExpiresAt().should.be.an.instanceOf(Date); }); @@ -136,7 +136,7 @@ describe('AbstractGrantType integration', function() { describe('getRefreshTokenExpiresAt()', function() { it('should return a refresh token', function() { - var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); + const handler = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); handler.getRefreshTokenExpiresAt().should.be.an.instanceOf(Date); }); @@ -144,8 +144,8 @@ describe('AbstractGrantType integration', function() { 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: {} }); + const handler = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); + const request = new Request({ body: { scope: 'ø倣‰' }, headers: {}, method: {}, query: {} }); try { handler.getScope(request); @@ -158,15 +158,15 @@ describe('AbstractGrantType integration', function() { }); 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: {} }); + 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', function() { - var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); - var request = new Request({ body: { scope: 'foo' }, headers: {}, method: {}, query: {} }); + 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/authorization-code-grant-type_test.js b/test/integration/grant-types/authorization-code-grant-type_test.js index 7f84e3443..6cddd53fc 100644 --- a/test/integration/grant-types/authorization-code-grant-type_test.js +++ b/test/integration/grant-types/authorization-code-grant-type_test.js @@ -4,14 +4,14 @@ * 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'); +const AuthorizationCodeGrantType = require('../../../lib/grant-types/authorization-code-grant-type'); +const InvalidArgumentError = require('../../../lib/errors/invalid-argument-error'); +const InvalidGrantError = require('../../../lib/errors/invalid-grant-error'); +const InvalidRequestError = require('../../../lib/errors/invalid-request-error'); +const Promise = require('bluebird'); +const Request = require('../../../lib/request'); +const ServerError = require('../../../lib/errors/server-error'); +const should = require('chai').should(); /** * Test `AuthorizationCodeGrantType` integration. @@ -43,7 +43,7 @@ describe('AuthorizationCodeGrantType integration', function() { it('should throw an error if the model does not implement `revokeAuthorizationCode()`', function() { try { - var model = { + const model = { getAuthorizationCode: function() {} }; @@ -58,7 +58,7 @@ describe('AuthorizationCodeGrantType integration', function() { it('should throw an error if the model does not implement `saveToken()`', function() { try { - var model = { + const model = { getAuthorizationCode: function() {}, revokeAuthorizationCode: function() {} }; @@ -75,12 +75,12 @@ describe('AuthorizationCodeGrantType integration', function() { describe('handle()', function() { it('should throw an error if `request` is missing', function() { - var model = { + const model = { getAuthorizationCode: function() {}, revokeAuthorizationCode: function() {}, saveToken: function() {} }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); + const grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); try { grantType.handle(); @@ -93,14 +93,14 @@ describe('AuthorizationCodeGrantType integration', function() { }); it('should throw an error if `client` is invalid', function() { - var client = {}; - var model = { + const client = {}; + const 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: {} }); + const grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); + const request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); return grantType.handle(request, client) .then(should.fail) @@ -112,13 +112,13 @@ describe('AuthorizationCodeGrantType integration', function() { it('should throw an error if `client` is missing', function() { - var model = { + const 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: {} }); + const grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); + const request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); try { grantType.handle(request, null); @@ -130,16 +130,16 @@ describe('AuthorizationCodeGrantType integration', function() { }); it('should return a token', function() { - var client = { id: 'foobar' }; - var token = {}; - var model = { + const client = { id: 'foobar' }; + const token = {}; + const 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: {} }); + const grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); + const request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); return grantType.handle(request, client) .then(function(data) { @@ -149,40 +149,40 @@ describe('AuthorizationCodeGrantType integration', function() { }); it('should support promises', function() { - var client = { id: 'foobar' }; - var model = { + const client = { id: 'foobar' }; + const 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: {} }); + const grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: 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', function() { - var client = { id: 'foobar' }; - var model = { + const client = { id: 'foobar' }; + const 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: {} }); + const grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); + const 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 = { + const client = { id: 'foobar' }; + const 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: {} }); + const grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); + const request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); grantType.handle(request, client).should.be.an.instanceOf(Promise); }); @@ -190,14 +190,14 @@ describe('AuthorizationCodeGrantType integration', function() { describe('getAuthorizationCode()', function() { it('should throw an error if the request body does not contain `code`', function() { - var client = {}; - var model = { + const client = {}; + const model = { getAuthorizationCode: function() {}, revokeAuthorizationCode: function() {}, saveToken: function() {} }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); + const grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); + const request = new Request({ body: {}, headers: {}, method: {}, query: {} }); try { grantType.getAuthorizationCode(request, client); @@ -210,14 +210,14 @@ describe('AuthorizationCodeGrantType integration', function() { }); it('should throw an error if `code` is invalid', function() { - var client = {}; - var model = { + const client = {}; + const model = { getAuthorizationCode: function() {}, revokeAuthorizationCode: function() {}, saveToken: function() {} }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 'ø倣‰' }, headers: {}, method: {}, query: {} }); + const grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); + const request = new Request({ body: { code: 'ø倣‰' }, headers: {}, method: {}, query: {} }); try { grantType.getAuthorizationCode(request, client); @@ -230,14 +230,14 @@ describe('AuthorizationCodeGrantType integration', function() { }); it('should throw an error if `authorizationCode` is missing', function() { - var client = {}; - var model = { + const client = {}; + const 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: {} }); + const grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); + const request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); return grantType.getAuthorizationCode(request, client) .then(should.fail) @@ -248,14 +248,14 @@ describe('AuthorizationCodeGrantType integration', function() { }); it('should throw an error if `authorizationCode.client` is missing', function() { - var client = {}; - var model = { + const client = {}; + const 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: {} }); + const grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); + const request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); return grantType.getAuthorizationCode(request, client) .then(should.fail) @@ -266,14 +266,14 @@ describe('AuthorizationCodeGrantType integration', function() { }); it('should throw an error if `authorizationCode.expiresAt` is missing', function() { - var client = {}; - var model = { + const client = {}; + const 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: {} }); + const grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); + const request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); return grantType.getAuthorizationCode(request, client) .then(should.fail) @@ -284,14 +284,14 @@ describe('AuthorizationCodeGrantType integration', function() { }); it('should throw an error if `authorizationCode.user` is missing', function() { - var client = {}; - var model = { + const client = {}; + const 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: {} }); + const grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); + const request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); return grantType.getAuthorizationCode(request, client) .then(should.fail) @@ -302,16 +302,16 @@ describe('AuthorizationCodeGrantType integration', function() { }); it('should throw an error if the client id does not match', function() { - var client = { id: 123 }; - var model = { + const client = { id: 123 }; + const 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: {} }); + const grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); + const request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); return grantType.getAuthorizationCode(request, client) .then(should.fail) @@ -322,17 +322,17 @@ describe('AuthorizationCodeGrantType integration', function() { }); 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 = { + const client = { id: 123 }; + const date = new Date(new Date() / 2); + const 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: {} }); + const grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); + const request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); return grantType.getAuthorizationCode(request, client) .then(should.fail) @@ -343,15 +343,15 @@ describe('AuthorizationCodeGrantType integration', function() { }); 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 = { + const authorizationCode = { authorizationCode: 12345, client: { id: 'foobar' }, expiresAt: new Date(new Date() * 2), redirectUri: 'foobar', user: {} }; + const client = { id: 'foobar' }; + const 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: {} }); + const grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); + const request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); return grantType.getAuthorizationCode(request, client) .then(should.fail) @@ -362,15 +362,15 @@ describe('AuthorizationCodeGrantType integration', function() { }); 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 = { + const authorizationCode = { authorizationCode: 12345, client: { id: 'foobar' }, expiresAt: new Date(new Date() * 2), user: {} }; + const client = { id: 'foobar' }; + const 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: {} }); + const grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); + const request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); return grantType.getAuthorizationCode(request, client) .then(function(data) { @@ -380,43 +380,43 @@ describe('AuthorizationCodeGrantType integration', function() { }); 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 = { + const authorizationCode = { authorizationCode: 12345, client: { id: 'foobar' }, expiresAt: new Date(new Date() * 2), user: {} }; + const client = { id: 'foobar' }; + const 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: {} }); + const grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: 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', function() { - var authorizationCode = { authorizationCode: 12345, client: { id: 'foobar' }, expiresAt: new Date(new Date() * 2), user: {} }; - var client = { id: 'foobar' }; - var model = { + const authorizationCode = { authorizationCode: 12345, client: { id: 'foobar' }, expiresAt: new Date(new Date() * 2), user: {} }; + const client = { id: 'foobar' }; + const 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: {} }); + const grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); + const 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 = { + const authorizationCode = { authorizationCode: 12345, client: { id: 'foobar' }, expiresAt: new Date(new Date() * 2), user: {} }; + const client = { id: 'foobar' }; + const 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: {} }); + const grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); + const request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); grantType.getAuthorizationCode(request, client).should.be.an.instanceOf(Promise); }); @@ -424,14 +424,14 @@ describe('AuthorizationCodeGrantType integration', function() { 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 = { + const authorizationCode = { authorizationCode: 12345, client: {}, expiresAt: new Date(new Date() / 2), redirectUri: 'http://foo.bar', user: {} }; + const 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: {} }); + const grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); + const request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); try { grantType.validateRedirectUri(request, authorizationCode); @@ -444,14 +444,14 @@ describe('AuthorizationCodeGrantType integration', function() { }); 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 = { + const authorizationCode = { authorizationCode: 12345, client: {}, expiresAt: new Date(new Date() / 2), redirectUri: 'http://foo.bar', user: {} }; + const 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: {} }); + const grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); + const request = new Request({ body: { code: 12345, redirect_uri: 'http://bar.foo' }, headers: {}, method: {}, query: {} }); try { grantType.validateRedirectUri(request, authorizationCode); @@ -466,13 +466,13 @@ describe('AuthorizationCodeGrantType integration', function() { describe('revokeAuthorizationCode()', function() { it('should revoke the auth code', function() { - var authorizationCode = { authorizationCode: 12345, client: {}, expiresAt: new Date(new Date() / 2), user: {} }; - var model = { + const authorizationCode = { authorizationCode: 12345, client: {}, expiresAt: new Date(new Date() / 2), user: {} }; + const model = { getAuthorizationCode: function() {}, revokeAuthorizationCode: function() { return true; }, saveToken: function() {} }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); + const grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); return grantType.revokeAuthorizationCode(authorizationCode) .then(function(data) { @@ -482,13 +482,13 @@ describe('AuthorizationCodeGrantType integration', function() { }); 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 = { + const authorizationCode = { authorizationCode: 12345, client: {}, expiresAt: new Date(new Date() / 2), user: {} }; + const model = { getAuthorizationCode: function() {}, revokeAuthorizationCode: function() { return false; }, saveToken: function() {} }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); + const grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); return grantType.revokeAuthorizationCode(authorizationCode) .then(function(data) { @@ -501,37 +501,37 @@ describe('AuthorizationCodeGrantType integration', function() { }); it('should support promises', function() { - var authorizationCode = { authorizationCode: 12345, client: {}, expiresAt: new Date(new Date() / 2), user: {} }; - var model = { + const authorizationCode = { authorizationCode: 12345, client: {}, expiresAt: new Date(new Date() / 2), user: {} }; + const model = { getAuthorizationCode: function() {}, revokeAuthorizationCode: function() { return Promise.resolve(true); }, saveToken: function() {} }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); + const 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 = { + const authorizationCode = { authorizationCode: 12345, client: {}, expiresAt: new Date(new Date() / 2), user: {} }; + const model = { getAuthorizationCode: function() {}, revokeAuthorizationCode: function() { return authorizationCode; }, saveToken: function() {} }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); + const 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 = { + const authorizationCode = { authorizationCode: 12345, client: {}, expiresAt: new Date(new Date() / 2), user: {} }; + const model = { getAuthorizationCode: function() {}, revokeAuthorizationCode: function(code, callback) { callback(null, authorizationCode); }, saveToken: function() {} }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); + const grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); grantType.revokeAuthorizationCode(authorizationCode).should.be.an.instanceOf(Promise); }); @@ -539,14 +539,14 @@ describe('AuthorizationCodeGrantType integration', function() { describe('saveToken()', function() { it('should save the token', function() { - var token = {}; - var model = { + const token = {}; + const model = { getAuthorizationCode: function() {}, revokeAuthorizationCode: function() {}, saveToken: function() { return token; }, validateScope: function() { return 'foo'; } }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); + const grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); return grantType.saveToken(token) .then(function(data) { @@ -556,37 +556,37 @@ describe('AuthorizationCodeGrantType integration', function() { }); it('should support promises', function() { - var token = {}; - var model = { + const token = {}; + const model = { getAuthorizationCode: function() {}, revokeAuthorizationCode: function() {}, saveToken: function() { return Promise.resolve(token); } }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); + const 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 = { + const token = {}; + const model = { getAuthorizationCode: function() {}, revokeAuthorizationCode: function() {}, saveToken: function() { return token; } }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); + const grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); grantType.saveToken(token).should.be.an.instanceOf(Promise); }); it('should support callbacks', function() { - var token = {}; - var model = { + const token = {}; + const model = { getAuthorizationCode: function() {}, revokeAuthorizationCode: function() {}, saveToken: function(tokenToSave, client, user, callback) { callback(null, token); } }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); + const 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_test.js b/test/integration/grant-types/client-credentials-grant-type_test.js index 15ec9cc0e..b13df0862 100644 --- a/test/integration/grant-types/client-credentials-grant-type_test.js +++ b/test/integration/grant-types/client-credentials-grant-type_test.js @@ -4,12 +4,12 @@ * 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'); +const ClientCredentialsGrantType = require('../../../lib/grant-types/client-credentials-grant-type'); +const InvalidArgumentError = require('../../../lib/errors/invalid-argument-error'); +const InvalidGrantError = require('../../../lib/errors/invalid-grant-error'); +const Promise = require('bluebird'); +const Request = require('../../../lib/request'); +const should = require('chai').should(); /** * Test `ClientCredentialsGrantType` integration. @@ -41,7 +41,7 @@ describe('ClientCredentialsGrantType integration', function() { it('should throw an error if the model does not implement `saveToken()`', function() { try { - var model = { + const model = { getUserFromClient: function() {} }; @@ -57,11 +57,11 @@ describe('ClientCredentialsGrantType integration', function() { describe('handle()', function() { it('should throw an error if `request` is missing', function() { - var model = { + const model = { getUserFromClient: function() {}, saveToken: function() {} }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); + const grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); try { grantType.handle(); @@ -74,12 +74,12 @@ describe('ClientCredentialsGrantType integration', function() { }); it('should throw an error if `client` is missing', function() { - var model = { + const model = { getUserFromClient: function() {}, saveToken: function() {} }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); + const grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); + const request = new Request({ body: {}, headers: {}, method: {}, query: {} }); try { grantType.handle(request); @@ -92,14 +92,14 @@ describe('ClientCredentialsGrantType integration', function() { }); it('should return a token', function() { - var token = {}; - var model = { + const token = {}; + const 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: {} }); + const grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); + const request = new Request({ body: {}, headers: {}, method: {}, query: {} }); return grantType.handle(request, {}) .then(function(data) { @@ -109,25 +109,25 @@ describe('ClientCredentialsGrantType integration', function() { }); it('should support promises', function() { - var token = {}; - var model = { + const token = {}; + const model = { getUserFromClient: function() { return {}; }, saveToken: function() { return token; } }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); + const grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); + const 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 = { + const token = {}; + const model = { getUserFromClient: function() { return {}; }, saveToken: function() { return token; } }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); + const grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); + const request = new Request({ body: {}, headers: {}, method: {}, query: {} }); grantType.handle(request, {}).should.be.an.instanceOf(Promise); }); @@ -135,12 +135,12 @@ describe('ClientCredentialsGrantType integration', function() { describe('getUserFromClient()', function() { it('should throw an error if `user` is missing', function() { - var model = { + const model = { getUserFromClient: function() {}, saveToken: function() {} }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); + const grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); + const request = new Request({ body: {}, headers: {}, method: {}, query: {} }); return grantType.getUserFromClient(request, {}) .then(should.fail) @@ -151,13 +151,13 @@ describe('ClientCredentialsGrantType integration', function() { }); it('should return a user', function() { - var user = { email: 'foo@bar.com' }; - var model = { + const user = { email: 'foo@bar.com' }; + const model = { getUserFromClient: function() { return user; }, saveToken: function() {} }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); + const grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); + const request = new Request({ body: {}, headers: {}, method: {}, query: {} }); return grantType.getUserFromClient(request, {}) .then(function(data) { @@ -167,37 +167,37 @@ describe('ClientCredentialsGrantType integration', function() { }); it('should support promises', function() { - var user = { email: 'foo@bar.com' }; - var model = { + const user = { email: 'foo@bar.com' }; + const 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: {} }); + const grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); + const 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 = { + const user = { email: 'foo@bar.com' }; + const model = { getUserFromClient: function() {return user; }, saveToken: function() {} }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); + const grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); + const 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 = { + const user = { email: 'foo@bar.com' }; + const 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: {} }); + const grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); + const request = new Request({ body: {}, headers: {}, method: {}, query: {} }); grantType.getUserFromClient(request, {}).should.be.an.instanceOf(Promise); }); @@ -205,13 +205,13 @@ describe('ClientCredentialsGrantType integration', function() { describe('saveToken()', function() { it('should save the token', function() { - var token = {}; - var model = { + const token = {}; + const model = { getUserFromClient: function() {}, saveToken: function() { return token; }, validateScope: function() { return 'foo'; } }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 123, model: model }); + const grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 123, model: model }); return grantType.saveToken(token) .then(function(data) { @@ -221,34 +221,34 @@ describe('ClientCredentialsGrantType integration', function() { }); it('should support promises', function() { - var token = {}; - var model = { + const token = {}; + const model = { getUserFromClient: function() {}, saveToken: function() { return Promise.resolve(token); } }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 123, model: model }); + const 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 = { + const token = {}; + const model = { getUserFromClient: function() {}, saveToken: function() { return token; } }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 123, model: model }); + const grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 123, model: model }); grantType.saveToken(token).should.be.an.instanceOf(Promise); }); it('should support callbacks', function() { - var token = {}; - var model = { + const token = {}; + const model = { getUserFromClient: function() {}, saveToken: function(tokenToSave, client, user, callback) { callback(null, token); } }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 123, model: model }); + const 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_test.js b/test/integration/grant-types/password-grant-type_test.js index 9cdf50dd5..a8c4cdaa4 100644 --- a/test/integration/grant-types/password-grant-type_test.js +++ b/test/integration/grant-types/password-grant-type_test.js @@ -4,13 +4,13 @@ * 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'); +const InvalidArgumentError = require('../../../lib/errors/invalid-argument-error'); +const InvalidGrantError = require('../../../lib/errors/invalid-grant-error'); +const InvalidRequestError = require('../../../lib/errors/invalid-request-error'); +const PasswordGrantType = require('../../../lib/grant-types/password-grant-type'); +const Promise = require('bluebird'); +const Request = require('../../../lib/request'); +const should = require('chai').should(); /** * Test `PasswordGrantType` integration. @@ -42,7 +42,7 @@ describe('PasswordGrantType integration', function() { it('should throw an error if the model does not implement `saveToken()`', function() { try { - var model = { + const model = { getUser: function() {} }; @@ -58,11 +58,11 @@ describe('PasswordGrantType integration', function() { describe('handle()', function() { it('should throw an error if `request` is missing', function() { - var model = { + const model = { getUser: function() {}, saveToken: function() {} }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); + const grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); try { grantType.handle(); @@ -75,11 +75,11 @@ describe('PasswordGrantType integration', function() { }); it('should throw an error if `client` is missing', function() { - var model = { + const model = { getUser: function() {}, saveToken: function() {} }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); + const grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); try { grantType.handle({}); @@ -92,15 +92,15 @@ describe('PasswordGrantType integration', function() { }); it('should return a token', function() { - var client = { id: 'foobar' }; - var token = {}; - var model = { + const client = { id: 'foobar' }; + const token = {}; + const 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: {} }); + const grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); + const request = new Request({ body: { username: 'foo', password: 'bar', scope: 'baz' }, headers: {}, method: {}, query: {} }); return grantType.handle(request, client) .then(function(data) { @@ -110,40 +110,40 @@ describe('PasswordGrantType integration', function() { }); it('should support promises', function() { - var client = { id: 'foobar' }; - var token = {}; - var model = { + const client = { id: 'foobar' }; + const token = {}; + const 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: {} }); + const grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: 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', function() { - var client = { id: 'foobar' }; - var token = {}; - var model = { + const client = { id: 'foobar' }; + const token = {}; + const 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: {} }); + const grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: 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', function() { - var client = { id: 'foobar' }; - var token = {}; - var model = { + const client = { id: 'foobar' }; + const token = {}; + const 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: {} }); + const grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); + const request = new Request({ body: { username: 'foo', password: 'bar' }, headers: {}, method: {}, query: {} }); grantType.handle(request, client).should.be.an.instanceOf(Promise); }); @@ -151,12 +151,12 @@ describe('PasswordGrantType integration', function() { describe('getUser()', function() { it('should throw an error if the request body does not contain `username`', function() { - var model = { + const model = { getUser: function() {}, saveToken: function() {} }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); + const grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); + const request = new Request({ body: {}, headers: {}, method: {}, query: {} }); try { grantType.getUser(request); @@ -169,12 +169,12 @@ describe('PasswordGrantType integration', function() { }); it('should throw an error if the request body does not contain `password`', function() { - var model = { + const model = { getUser: function() {}, saveToken: function() {} }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { username: 'foo' }, headers: {}, method: {}, query: {} }); + const grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); + const request = new Request({ body: { username: 'foo' }, headers: {}, method: {}, query: {} }); try { grantType.getUser(request); @@ -187,12 +187,12 @@ describe('PasswordGrantType integration', function() { }); it('should throw an error if `username` is invalid', function() { - var model = { + const 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: {} }); + const grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); + const request = new Request({ body: { username: '\r\n', password: 'foobar' }, headers: {}, method: {}, query: {} }); try { grantType.getUser(request); @@ -205,12 +205,12 @@ describe('PasswordGrantType integration', function() { }); it('should throw an error if `password` is invalid', function() { - var model = { + const 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: {} }); + const grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); + const request = new Request({ body: { username: 'foobar', password: '\r\n' }, headers: {}, method: {}, query: {} }); try { grantType.getUser(request); @@ -223,12 +223,12 @@ describe('PasswordGrantType integration', function() { }); it('should throw an error if `user` is missing', function() { - var model = { + const 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: {} }); + const grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); + const request = new Request({ body: { username: 'foo', password: 'bar' }, headers: {}, method: {}, query: {} }); return grantType.getUser(request) .then(should.fail) @@ -239,13 +239,13 @@ describe('PasswordGrantType integration', function() { }); it('should return a user', function() { - var user = { email: 'foo@bar.com' }; - var model = { + const user = { email: 'foo@bar.com' }; + const 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: {} }); + const grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); + const request = new Request({ body: { username: 'foo', password: 'bar' }, headers: {}, method: {}, query: {} }); return grantType.getUser(request) .then(function(data) { @@ -255,37 +255,37 @@ describe('PasswordGrantType integration', function() { }); it('should support promises', function() { - var user = { email: 'foo@bar.com' }; - var model = { + const user = { email: 'foo@bar.com' }; + const 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: {} }); + const grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: 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', function() { - var user = { email: 'foo@bar.com' }; - var model = { + const user = { email: 'foo@bar.com' }; + const 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: {} }); + const grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: 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', function() { - var user = { email: 'foo@bar.com' }; - var model = { + const user = { email: 'foo@bar.com' }; + const 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: {} }); + const grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); + const request = new Request({ body: { username: 'foo', password: 'bar' }, headers: {}, method: {}, query: {} }); grantType.getUser(request).should.be.an.instanceOf(Promise); }); @@ -293,13 +293,13 @@ describe('PasswordGrantType integration', function() { describe('saveToken()', function() { it('should save the token', function() { - var token = {}; - var model = { + const token = {}; + const model = { getUser: function() {}, saveToken: function() { return token; }, validateScope: function() { return 'foo'; } }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); + const grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); return grantType.saveToken(token) .then(function(data) { @@ -309,34 +309,34 @@ describe('PasswordGrantType integration', function() { }); it('should support promises', function() { - var token = {}; - var model = { + const token = {}; + const model = { getUser: function() {}, saveToken: function() { return Promise.resolve(token); } }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); + const 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 = { + const token = {}; + const model = { getUser: function() {}, saveToken: function() { return token; } }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); + const grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); grantType.saveToken(token).should.be.an.instanceOf(Promise); }); it('should support callbacks', function() { - var token = {}; - var model = { + const token = {}; + const model = { getUser: function() {}, saveToken: function(tokenToSave, client, user, callback) { callback(null, token); } }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); + const 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_test.js b/test/integration/grant-types/refresh-token-grant-type_test.js index 925396afe..945d51c4f 100644 --- a/test/integration/grant-types/refresh-token-grant-type_test.js +++ b/test/integration/grant-types/refresh-token-grant-type_test.js @@ -4,14 +4,14 @@ * 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'); +const InvalidArgumentError = require('../../../lib/errors/invalid-argument-error'); +const InvalidGrantError = require('../../../lib/errors/invalid-grant-error'); +const InvalidRequestError = require('../../../lib/errors/invalid-request-error'); +const Promise = require('bluebird'); +const RefreshTokenGrantType = require('../../../lib/grant-types/refresh-token-grant-type'); +const Request = require('../../../lib/request'); +const ServerError = require('../../../lib/errors/server-error'); +const should = require('chai').should(); /** * Test `RefreshTokenGrantType` integration. @@ -43,7 +43,7 @@ describe('RefreshTokenGrantType integration', function() { it('should throw an error if the model does not implement `revokeToken()`', function() { try { - var model = { + const model = { getRefreshToken: function() {} }; @@ -58,7 +58,7 @@ describe('RefreshTokenGrantType integration', function() { it('should throw an error if the model does not implement `saveToken()`', function() { try { - var model = { + const model = { getRefreshToken: function() {}, revokeToken: function() {} }; @@ -75,12 +75,12 @@ describe('RefreshTokenGrantType integration', function() { describe('handle()', function() { it('should throw an error if `request` is missing', function() { - var model = { + const model = { getRefreshToken: function() {}, revokeToken: function() {}, saveToken: function() {} }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); + const grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); try { grantType.handle(); @@ -93,13 +93,13 @@ describe('RefreshTokenGrantType integration', function() { }); it('should throw an error if `client` is missing', function() { - var model = { + const model = { getRefreshToken: function() {}, revokeToken: function() {}, saveToken: function() {} }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); + const grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); + const request = new Request({ body: {}, headers: {}, method: {}, query: {} }); try { grantType.handle(request); @@ -112,15 +112,15 @@ describe('RefreshTokenGrantType integration', function() { }); it('should return a token', function() { - var client = { id: 123 }; - var token = { accessToken: 'foo', client: { id: 123 }, user: {} }; - var model = { + const client = { id: 123 }; + const token = { accessToken: 'foo', client: { id: 123 }, user: {} }; + const 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: {} }); + const grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); + const request = new Request({ body: { refresh_token: 'foobar' }, headers: {}, method: {}, query: {} }); return grantType.handle(request, client) .then(function(data) { @@ -130,40 +130,40 @@ describe('RefreshTokenGrantType integration', function() { }); it('should support promises', function() { - var client = { id: 123 }; - var model = { + const client = { id: 123 }; + const 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: {} }); + const grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: 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', function() { - var client = { id: 123 }; - var model = { + const client = { id: 123 }; + const 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: {} }); + const grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: 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', function() { - var client = { id: 123 }; - var model = { + const client = { id: 123 }; + const 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: {} }); + const grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); + const request = new Request({ body: { refresh_token: 'foobar' }, headers: {}, method: {}, query: {} }); grantType.handle(request, client).should.be.an.instanceOf(Promise); }); @@ -171,14 +171,14 @@ describe('RefreshTokenGrantType integration', function() { describe('getRefreshToken()', function() { it('should throw an error if the `refreshToken` parameter is missing from the request body', function() { - var client = {}; - var model = { + const client = {}; + const model = { getRefreshToken: function() {}, revokeToken: function() {}, saveToken: function() {} }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); + const grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); + const request = new Request({ body: {}, headers: {}, method: {}, query: {} }); try { grantType.getRefreshToken(request, client); @@ -191,14 +191,14 @@ describe('RefreshTokenGrantType integration', function() { }); it('should throw an error if `refreshToken` is not found', function() { - var client = { id: 123 }; - var model = { + const client = { id: 123 }; + const 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: {} }); + const grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); + const request = new Request({ body: { refresh_token: '12345' }, headers: {}, method: {}, query: {} }); return grantType.getRefreshToken(request, client) .then(should.fail) @@ -209,14 +209,14 @@ describe('RefreshTokenGrantType integration', function() { }); it('should throw an error if `refreshToken.client` is missing', function() { - var client = {}; - var model = { + const client = {}; + const 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: {} }); + const grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); + const request = new Request({ body: { refresh_token: 12345 }, headers: {}, method: {}, query: {} }); return grantType.getRefreshToken(request, client) .then(should.fail) @@ -227,16 +227,16 @@ describe('RefreshTokenGrantType integration', function() { }); it('should throw an error if `refreshToken.user` is missing', function() { - var client = {}; - var model = { + const client = {}; + const 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: {} }); + const grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); + const request = new Request({ body: { refresh_token: 12345 }, headers: {}, method: {}, query: {} }); return grantType.getRefreshToken(request, client) .then(should.fail) @@ -247,16 +247,16 @@ describe('RefreshTokenGrantType integration', function() { }); it('should throw an error if the client id does not match', function() { - var client = { id: 123 }; - var model = { + const client = { id: 123 }; + const 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: {} }); + const grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); + const request = new Request({ body: { refresh_token: 12345 }, headers: {}, method: {}, query: {} }); return grantType.getRefreshToken(request, client) .then(should.fail) @@ -267,16 +267,16 @@ describe('RefreshTokenGrantType integration', function() { }); it('should throw an error if `refresh_token` contains invalid characters', function() { - var client = {}; - var model = { + const client = {}; + const 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: {} }); + const grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); + const request = new Request({ body: { refresh_token: 'ø倣‰' }, headers: {}, method: {}, query: {} }); try { grantType.getRefreshToken(request, client); @@ -289,16 +289,16 @@ describe('RefreshTokenGrantType integration', function() { }); it('should throw an error if `refresh_token` is missing', function() { - var client = {}; - var model = { + const client = {}; + const 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: {} }); + const grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); + const request = new Request({ body: { refresh_token: 12345 }, headers: {}, method: {}, query: {} }); return grantType.getRefreshToken(request, client) .then(should.fail) @@ -309,17 +309,17 @@ describe('RefreshTokenGrantType integration', function() { }); it('should throw an error if `refresh_token` is expired', function() { - var client = { id: 123 }; - var date = new Date(new Date() / 2); - var model = { + const client = { id: 123 }; + const date = new Date(new Date() / 2); + const 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: {} }); + const grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); + const request = new Request({ body: { refresh_token: 12345 }, headers: {}, method: {}, query: {} }); return grantType.getRefreshToken(request, client) .then(should.fail) @@ -330,16 +330,16 @@ describe('RefreshTokenGrantType integration', function() { }); it('should throw an error if `refreshTokenExpiresAt` is not a date value', function() { - var client = { id: 123 }; - var model = { + const client = { id: 123 }; + const 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: {} }); + const grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); + const request = new Request({ body: { refresh_token: 12345 }, headers: {}, method: {}, query: {} }); return grantType.getRefreshToken(request, client) .then(should.fail) @@ -350,15 +350,15 @@ describe('RefreshTokenGrantType integration', function() { }); it('should return a token', function() { - var client = { id: 123 }; - var token = { accessToken: 'foo', client: { id: 123 }, user: {} }; - var model = { + const client = { id: 123 }; + const token = { accessToken: 'foo', client: { id: 123 }, user: {} }; + const 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: {} }); + const grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); + const request = new Request({ body: { refresh_token: 'foobar' }, headers: {}, method: {}, query: {} }); return grantType.getRefreshToken(request, client) .then(function(data) { @@ -368,43 +368,43 @@ describe('RefreshTokenGrantType integration', function() { }); it('should support promises', function() { - var client = { id: 123 }; - var token = { accessToken: 'foo', client: { id: 123 }, user: {} }; - var model = { + const client = { id: 123 }; + const token = { accessToken: 'foo', client: { id: 123 }, user: {} }; + const 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: {} }); + const grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: 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', function() { - var client = { id: 123 }; - var token = { accessToken: 'foo', client: { id: 123 }, user: {} }; - var model = { + const client = { id: 123 }; + const token = { accessToken: 'foo', client: { id: 123 }, user: {} }; + const 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: {} }); + const grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: 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', function() { - var client = { id: 123 }; - var token = { accessToken: 'foo', client: { id: 123 }, user: {} }; - var model = { + const client = { id: 123 }; + const token = { accessToken: 'foo', client: { id: 123 }, user: {} }; + const 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: {} }); + const grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); + const request = new Request({ body: { refresh_token: 'foobar' }, headers: {}, method: {}, query: {} }); grantType.getRefreshToken(request, client).should.be.an.instanceOf(Promise); }); @@ -412,12 +412,12 @@ describe('RefreshTokenGrantType integration', function() { describe('revokeToken()', function() { it('should throw an error if the `token` is invalid', function() { - var model = { + const model = { getRefreshToken: function() {}, revokeToken: function() {}, saveToken: function() {} }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); + const grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); grantType.revokeToken({}) .then(should.fail) @@ -428,13 +428,13 @@ describe('RefreshTokenGrantType integration', function() { }); it('should revoke the token', function() { - var token = { accessToken: 'foo', client: {}, refreshTokenExpiresAt: new Date(new Date() / 2), user: {} }; - var model = { + const token = { accessToken: 'foo', client: {}, refreshTokenExpiresAt: new Date(new Date() / 2), user: {} }; + const model = { getRefreshToken: function() {}, revokeToken: function() { return token; }, saveToken: function() {} }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); + const grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); return grantType.revokeToken(token) .then(function(data) { @@ -444,37 +444,37 @@ describe('RefreshTokenGrantType integration', function() { }); it('should support promises', function() { - var token = { accessToken: 'foo', client: {}, refreshTokenExpiresAt: new Date(new Date() / 2), user: {} }; - var model = { + const token = { accessToken: 'foo', client: {}, refreshTokenExpiresAt: new Date(new Date() / 2), user: {} }; + const model = { getRefreshToken: function() {}, revokeToken: function() { return Promise.resolve(token); }, saveToken: function() {} }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); + const 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 = { + const token = { accessToken: 'foo', client: {}, refreshTokenExpiresAt: new Date(new Date() / 2), user: {} }; + const model = { getRefreshToken: function() {}, revokeToken: function() { return token; }, saveToken: function() {} }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); + const 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 = { + const token = { accessToken: 'foo', client: {}, refreshTokenExpiresAt: new Date(new Date() / 2), user: {} }; + const model = { getRefreshToken: function() {}, revokeToken: function(refreshToken, callback) { callback(null, token); }, saveToken: function() {} }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); + const grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); grantType.revokeToken(token).should.be.an.instanceOf(Promise); }); @@ -482,13 +482,13 @@ describe('RefreshTokenGrantType integration', function() { describe('saveToken()', function() { it('should save the token', function() { - var token = {}; - var model = { + const token = {}; + const model = { getRefreshToken: function() {}, revokeToken: function() {}, saveToken: function() { return token; } }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); + const grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); return grantType.saveToken(token) .then(function(data) { @@ -498,37 +498,37 @@ describe('RefreshTokenGrantType integration', function() { }); it('should support promises', function() { - var token = {}; - var model = { + const token = {}; + const model = { getRefreshToken: function() {}, revokeToken: function() {}, saveToken: function() { return Promise.resolve(token); } }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); + const 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 = { + const token = {}; + const model = { getRefreshToken: function() {}, revokeToken: function() {}, saveToken: function() { return token; } }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); + const grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); grantType.saveToken(token).should.be.an.instanceOf(Promise); }); it('should support callbacks', function() { - var token = {}; - var model = { + const token = {}; + const model = { getRefreshToken: function() {}, revokeToken: function() {}, saveToken: function(tokenToSave, client, user, callback) { callback(null, token); } }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); + const grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); grantType.saveToken(token).should.be.an.instanceOf(Promise); }); diff --git a/test/integration/handlers/authenticate-handler_test.js b/test/integration/handlers/authenticate-handler_test.js index 7852ea2eb..151ada325 100644 --- a/test/integration/handlers/authenticate-handler_test.js +++ b/test/integration/handlers/authenticate-handler_test.js @@ -4,18 +4,18 @@ * 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'); +const AccessDeniedError = require('../../../lib/errors/access-denied-error'); +const AuthenticateHandler = require('../../../lib/handlers/authenticate-handler'); +const InvalidArgumentError = require('../../../lib/errors/invalid-argument-error'); +const InvalidRequestError = require('../../../lib/errors/invalid-request-error'); +const InsufficientScopeError = require('../../../lib/errors/insufficient-scope-error'); +const InvalidTokenError = require('../../../lib/errors/invalid-token-error'); +const Promise = require('bluebird'); +const Request = require('../../../lib/request'); +const Response = require('../../../lib/response'); +const ServerError = require('../../../lib/errors/server-error'); +const UnauthorizedRequestError = require('../../../lib/errors/unauthorized-request-error'); +const should = require('chai').should(); /** * Test `AuthenticateHandler` integration. @@ -79,18 +79,18 @@ describe('AuthenticateHandler integration', function() { }); it('should set the `model`', function() { - var model = { getAccessToken: function() {} }; - var grantType = new AuthenticateHandler({ model: model }); + const model = { getAccessToken: function() {} }; + const grantType = new AuthenticateHandler({ model: model }); grantType.model.should.equal(model); }); it('should set the `scope`', function() { - var model = { + const model = { getAccessToken: function() {}, verifyScope: function() {} }; - var grantType = new AuthenticateHandler({ + const grantType = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: true, model: model, @@ -103,7 +103,7 @@ describe('AuthenticateHandler integration', function() { describe('handle()', function() { it('should throw an error if `request` is missing', function() { - var handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); + const handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); try { handler.handle(); @@ -116,14 +116,14 @@ describe('AuthenticateHandler integration', function() { }); it('should set the `WWW-Authenticate` header if an unauthorized request error is thrown', function() { - var model = { + const 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: {} }); + const handler = new AuthenticateHandler({ model: 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) @@ -132,15 +132,66 @@ describe('AuthenticateHandler integration', function() { }); }); + it('should set the `WWW-Authenticate` header if an InvalidRequestError is thrown', function() { + const model = { + getAccessToken: function() { + throw new InvalidRequestError(); + } + }; + const handler = new AuthenticateHandler({ model: 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) + .catch(function() { + response.get('WWW-Authenticate').should.equal('Bearer realm="Service",error="invalid_request"'); + }); + }); + + it('should set the `WWW-Authenticate` header if an InvalidTokenError is thrown', function() { + const model = { + getAccessToken: function() { + throw new InvalidTokenError(); + } + }; + const handler = new AuthenticateHandler({ model: 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) + .catch(function() { + response.get('WWW-Authenticate').should.equal('Bearer realm="Service",error="invalid_token"'); + }); + }); + + it('should set the `WWW-Authenticate` header if an InsufficientScopeError is thrown', function() { + const model = { + getAccessToken: function() { + throw new InsufficientScopeError(); + } + }; + const handler = new AuthenticateHandler({ model: 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) + .catch(function() { + response.get('WWW-Authenticate').should.equal('Bearer realm="Service",error="insufficient_scope"'); + }); + }); + it('should throw the error if an oauth error is thrown', function() { - var model = { + const 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: {} }); + const handler = new AuthenticateHandler({ model: 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) @@ -151,14 +202,14 @@ describe('AuthenticateHandler integration', function() { }); it('should throw a server error if a non-oauth error is thrown', function() { - var model = { + const 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: {} }); + const handler = new AuthenticateHandler({ model: 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) @@ -169,11 +220,11 @@ describe('AuthenticateHandler integration', function() { }); it('should return an access token', function() { - var accessToken = { + const accessToken = { user: {}, accessTokenExpiresAt: new Date(new Date().getTime() + 10000) }; - var model = { + const model = { getAccessToken: function() { return accessToken; }, @@ -181,14 +232,14 @@ describe('AuthenticateHandler integration', function() { return true; } }; - var handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: true, model: model, scope: 'foo' }); - var request = new Request({ + const handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: true, model: model, scope: 'foo' }); + const request = new Request({ body: {}, headers: { 'Authorization': 'Bearer foo' }, method: {}, query: {} }); - var response = new Response({ body: {}, headers: {} }); + const response = new Response({ body: {}, headers: {} }); return handler.handle(request, response) .then(function(data) { @@ -200,8 +251,8 @@ describe('AuthenticateHandler integration', function() { 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({ + const handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); + const request = new Request({ body: {}, headers: { 'Authorization': 'Bearer foo' }, method: {}, @@ -219,8 +270,8 @@ describe('AuthenticateHandler integration', function() { }); 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: {} }); + const handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); + const request = new Request({ body: {}, headers: {}, method: {}, query: {} }); try { handler.getTokenFromRequest(request); @@ -235,8 +286,8 @@ describe('AuthenticateHandler integration', function() { 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({ + const handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); + const request = new Request({ body: {}, headers: { 'Authorization': 'foobar' @@ -256,8 +307,8 @@ describe('AuthenticateHandler integration', function() { }); it('should return the bearer token', function() { - var handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); - var request = new Request({ + const handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); + const request = new Request({ body: {}, headers: { 'Authorization': 'Bearer foo' @@ -266,7 +317,7 @@ describe('AuthenticateHandler integration', function() { query: {} }); - var bearerToken = handler.getTokenFromRequestHeader(request); + const bearerToken = handler.getTokenFromRequestHeader(request); bearerToken.should.equal('foo'); }); @@ -274,7 +325,7 @@ describe('AuthenticateHandler integration', function() { describe('getTokenFromRequestQuery()', function() { it('should throw an error if the query contains a token', function() { - var handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); + const handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); try { handler.getTokenFromRequestQuery(); @@ -287,7 +338,7 @@ describe('AuthenticateHandler integration', function() { }); it('should return the bearer token if `allowBearerTokensInQueryString` is true', function() { - var handler = new AuthenticateHandler({ allowBearerTokensInQueryString: true, model: { getAccessToken: function() {} } }); + const handler = new AuthenticateHandler({ allowBearerTokensInQueryString: true, model: { getAccessToken: function() {} } }); handler.getTokenFromRequestQuery({ query: { access_token: 'foo' } }).should.equal('foo'); }); @@ -295,8 +346,8 @@ describe('AuthenticateHandler integration', function() { 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({ + const handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); + const request = new Request({ body: { access_token: 'foo' }, headers: {}, method: 'GET', @@ -314,8 +365,8 @@ describe('AuthenticateHandler integration', function() { }); 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({ + const handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); + const request = new Request({ body: { access_token: 'foo' }, headers: {}, method: {}, @@ -333,8 +384,8 @@ describe('AuthenticateHandler integration', function() { }); it('should return the bearer token', function() { - var handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); - var request = new Request({ + const handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); + const request = new Request({ body: { access_token: 'foo' }, headers: { 'content-type': 'application/x-www-form-urlencoded', 'transfer-encoding': 'chunked' }, method: {}, @@ -347,10 +398,10 @@ describe('AuthenticateHandler integration', function() { describe('getAccessToken()', function() { it('should throw an error if `accessToken` is missing', function() { - var model = { + const model = { getAccessToken: function() {} }; - var handler = new AuthenticateHandler({ model: model }); + const handler = new AuthenticateHandler({ model: model }); return handler.getAccessToken('foo') .then(should.fail) @@ -361,12 +412,12 @@ describe('AuthenticateHandler integration', function() { }); it('should throw an error if `accessToken.user` is missing', function() { - var model = { + const model = { getAccessToken: function() { return {}; } }; - var handler = new AuthenticateHandler({ model: model }); + const handler = new AuthenticateHandler({ model: model }); return handler.getAccessToken('foo') .then(should.fail) @@ -377,13 +428,13 @@ describe('AuthenticateHandler integration', function() { }); it('should return an access token', function() { - var accessToken = { user: {} }; - var model = { + const accessToken = { user: {} }; + const model = { getAccessToken: function() { return accessToken; } }; - var handler = new AuthenticateHandler({ model: model }); + const handler = new AuthenticateHandler({ model: model }); return handler.getAccessToken('foo') .then(function(data) { @@ -393,34 +444,34 @@ describe('AuthenticateHandler integration', function() { }); it('should support promises', function() { - var model = { + const model = { getAccessToken: function() { return Promise.resolve({ user: {} }); } }; - var handler = new AuthenticateHandler({ model: model }); + const handler = new AuthenticateHandler({ model: model }); handler.getAccessToken('foo').should.be.an.instanceOf(Promise); }); it('should support non-promises', function() { - var model = { + const model = { getAccessToken: function() { return { user: {} }; } }; - var handler = new AuthenticateHandler({ model: model }); + const handler = new AuthenticateHandler({ model: model }); handler.getAccessToken('foo').should.be.an.instanceOf(Promise); }); it('should support callbacks', function() { - var model = { + const model = { getAccessToken: function(token, callback) { callback(null, { user: {} }); } }; - var handler = new AuthenticateHandler({ model: model }); + const handler = new AuthenticateHandler({ model: model }); handler.getAccessToken('foo').should.be.an.instanceOf(Promise); }); @@ -428,8 +479,8 @@ describe('AuthenticateHandler integration', function() { 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() {} } }); + const accessToken = { accessTokenExpiresAt: new Date(new Date() / 2) }; + const handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); try { handler.validateAccessToken(accessToken); @@ -442,11 +493,11 @@ describe('AuthenticateHandler integration', function() { }); it('should return an access token', function() { - var accessToken = { + const accessToken = { user: {}, accessTokenExpiresAt: new Date(new Date().getTime() + 10000) }; - var handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); + const handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); handler.validateAccessToken(accessToken).should.equal(accessToken); }); @@ -454,13 +505,13 @@ describe('AuthenticateHandler integration', function() { describe('verifyScope()', function() { it('should throw an error if `scope` is insufficient', function() { - var model = { + const model = { getAccessToken: function() {}, verifyScope: function() { return false; } }; - var handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: true, model: model, scope: 'foo' }); + const handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: true, model: model, scope: 'foo' }); return handler.verifyScope('foo') .then(should.fail) @@ -471,37 +522,37 @@ describe('AuthenticateHandler integration', function() { }); it('should support promises', function() { - var model = { + const model = { getAccessToken: function() {}, verifyScope: function() { return true; } }; - var handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: true, model: model, scope: 'foo' }); + const 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 = { + const model = { getAccessToken: function() {}, verifyScope: function() { return true; } }; - var handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: true, model: model, scope: 'foo' }); + const 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 = { + const model = { getAccessToken: function() {}, verifyScope: function(token, scope, callback) { callback(null, true); } }; - var handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: true, model: model, scope: 'foo' }); + const handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: true, model: model, scope: 'foo' }); handler.verifyScope('foo').should.be.an.instanceOf(Promise); }); @@ -509,12 +560,12 @@ describe('AuthenticateHandler integration', function() { describe('updateResponse()', function() { it('should not set the `X-Accepted-OAuth-Scopes` header if `scope` is not specified', function() { - var model = { + const model = { getAccessToken: function() {}, verifyScope: function() {} }; - var handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: false, model: model }); - var response = new Response({ body: {}, headers: {} }); + const handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: false, model: model }); + const response = new Response({ body: {}, headers: {} }); handler.updateResponse(response, { scope: 'foo biz' }); @@ -522,12 +573,12 @@ describe('AuthenticateHandler integration', function() { }); it('should set the `X-Accepted-OAuth-Scopes` header if `scope` is specified', function() { - var model = { + const model = { getAccessToken: function() {}, verifyScope: function() {} }; - var handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: false, model: model, scope: 'foo bar' }); - var response = new Response({ body: {}, headers: {} }); + const handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: false, model: model, scope: 'foo bar' }); + const response = new Response({ body: {}, headers: {} }); handler.updateResponse(response, { scope: 'foo biz' }); @@ -535,12 +586,12 @@ describe('AuthenticateHandler integration', function() { }); it('should not set the `X-Authorized-OAuth-Scopes` header if `scope` is not specified', function() { - var model = { + const model = { getAccessToken: function() {}, verifyScope: function() {} }; - var handler = new AuthenticateHandler({ addAcceptedScopesHeader: false, addAuthorizedScopesHeader: true, model: model }); - var response = new Response({ body: {}, headers: {} }); + const handler = new AuthenticateHandler({ addAcceptedScopesHeader: false, addAuthorizedScopesHeader: true, model: model }); + const response = new Response({ body: {}, headers: {} }); handler.updateResponse(response, { scope: 'foo biz' }); @@ -548,12 +599,12 @@ describe('AuthenticateHandler integration', function() { }); it('should set the `X-Authorized-OAuth-Scopes` header', function() { - var model = { + const model = { getAccessToken: function() {}, verifyScope: function() {} }; - var handler = new AuthenticateHandler({ addAcceptedScopesHeader: false, addAuthorizedScopesHeader: true, model: model, scope: 'foo bar' }); - var response = new Response({ body: {}, headers: {} }); + const handler = new AuthenticateHandler({ addAcceptedScopesHeader: false, addAuthorizedScopesHeader: true, model: model, scope: 'foo bar' }); + const response = new Response({ body: {}, headers: {} }); handler.updateResponse(response, { scope: 'foo biz' }); diff --git a/test/integration/handlers/authorize-handler_test.js b/test/integration/handlers/authorize-handler_test.js index f895f82e3..054e2cc4f 100644 --- a/test/integration/handlers/authorize-handler_test.js +++ b/test/integration/handlers/authorize-handler_test.js @@ -4,22 +4,22 @@ * 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'); +const AccessDeniedError = require('../../../lib/errors/access-denied-error'); +const AuthenticateHandler = require('../../../lib/handlers/authenticate-handler'); +const AuthorizeHandler = require('../../../lib/handlers/authorize-handler'); +const CodeResponseType = require('../../../lib/response-types/code-response-type'); +const InvalidArgumentError = require('../../../lib/errors/invalid-argument-error'); +const InvalidClientError = require('../../../lib/errors/invalid-client-error'); +const InvalidRequestError = require('../../../lib/errors/invalid-request-error'); +const InvalidScopeError = require('../../../lib/errors/invalid-scope-error'); +const UnsupportedResponseTypeError = require('../../../lib/errors/unsupported-response-type-error'); +const Promise = require('bluebird'); +const Request = require('../../../lib/request'); +const Response = require('../../../lib/response'); +const ServerError = require('../../../lib/errors/server-error'); +const UnauthorizedClientError = require('../../../lib/errors/unauthorized-client-error'); +const should = require('chai').should(); +const url = require('url'); /** * Test `AuthorizeHandler` integration. @@ -72,7 +72,7 @@ describe('AuthorizeHandler integration', function() { }); it('should throw an error if the model does not implement `getAccessToken()`', function() { - var model = { + const model = { getClient: function() {}, saveAuthorizationCode: function() {} }; @@ -88,34 +88,34 @@ describe('AuthorizeHandler integration', function() { }); it('should set the `authorizationCodeLifetime`', function() { - var model = { + const model = { getAccessToken: function() {}, getClient: function() {}, saveAuthorizationCode: function() {} }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); handler.authorizationCodeLifetime.should.equal(120); }); it('should set the `authenticateHandler`', function() { - var model = { + const model = { getAccessToken: function() {}, getClient: function() {}, saveAuthorizationCode: function() {} }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); handler.authenticateHandler.should.be.an.instanceOf(AuthenticateHandler); }); it('should set the `model`', function() { - var model = { + const model = { getAccessToken: function() {}, getClient: function() {}, saveAuthorizationCode: function() {} }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); handler.model.should.equal(model); }); @@ -123,12 +123,12 @@ describe('AuthorizeHandler integration', function() { describe('handle()', function() { it('should throw an error if `request` is missing', function() { - var model = { + const model = { getAccessToken: function() {}, getClient: function() {}, saveAuthorizationCode: function() {} }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); try { handler.handle(); @@ -141,13 +141,13 @@ describe('AuthorizeHandler integration', function() { }); it('should throw an error if `response` is missing', function() { - var model = { + const model = { getAccessToken: function() {}, getClient: function() {}, saveAuthorizationCode: function() {} }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: {}, headers: {}, method: {}, query: {} }); try { handler.handle(request); @@ -160,14 +160,53 @@ describe('AuthorizeHandler integration', function() { }); it('should throw an error if `allowed` is `false`', function() { - var model = { + const 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'); + } + }; + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ + body: { + client_id: 'test' + }, + headers: { + 'Authorization': 'Bearer foo' + }, + method: {}, + query: { + allowed: 'false', + state: 'foobar' + } + }); + const 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 throw an error if `allowed` is `false` body', function() { + const 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: {} }); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: { allowed: 'false' }, headers: {}, method: {}, query: {} }); + const response = new Response({ body: {}, headers: {} }); return handler.handle(request, response) .then(should.fail) @@ -178,7 +217,7 @@ describe('AuthorizeHandler integration', function() { }); it('should redirect to an error response if a non-oauth error is thrown', function() { - var model = { + const model = { getAccessToken: function() { return { user: {}, @@ -192,8 +231,8 @@ describe('AuthorizeHandler integration', function() { throw new Error('Unhandled exception'); } }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: { client_id: 12345, response_type: 'code' @@ -206,7 +245,7 @@ describe('AuthorizeHandler integration', function() { state: 'foobar' } }); - var response = new Response({ body: {}, headers: {} }); + const response = new Response({ body: {}, headers: {} }); return handler.handle(request, response) .then(should.fail) @@ -216,7 +255,7 @@ describe('AuthorizeHandler integration', function() { }); it('should redirect to an error response if an oauth error is thrown', function() { - var model = { + const model = { getAccessToken: function() { return { user: {}, @@ -230,8 +269,8 @@ describe('AuthorizeHandler integration', function() { throw new AccessDeniedError('Cannot request this auth code'); } }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: { client_id: 12345, response_type: 'code' @@ -244,7 +283,7 @@ describe('AuthorizeHandler integration', function() { state: 'foobar' } }); - var response = new Response({ body: {}, headers: {} }); + const response = new Response({ body: {}, headers: {} }); return handler.handle(request, response) .then(should.fail) @@ -254,8 +293,8 @@ describe('AuthorizeHandler integration', function() { }); 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 = { + const client = { grants: ['authorization_code'], redirectUris: ['http://example.com/cb'] }; + const model = { getAccessToken: function() { return { client: client, @@ -270,8 +309,8 @@ describe('AuthorizeHandler integration', function() { return { authorizationCode: 12345, client: client }; } }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: { client_id: 12345, response_type: 'code' @@ -284,7 +323,7 @@ describe('AuthorizeHandler integration', function() { state: 'foobar' } }); - var response = new Response({ body: {}, headers: {} }); + const response = new Response({ body: {}, headers: {} }); return handler.handle(request, response) .then(function() { @@ -294,7 +333,7 @@ describe('AuthorizeHandler integration', function() { }); it('should redirect to an error response if `scope` is invalid', function() { - var model = { + const model = { getAccessToken: function() { return { user: {}, @@ -308,8 +347,8 @@ describe('AuthorizeHandler integration', function() { return {}; } }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: { client_id: 12345, response_type: 'code' @@ -323,18 +362,18 @@ describe('AuthorizeHandler integration', function() { state: 'foobar' } }); - var response = new Response({ body: {}, headers: {} }); + const 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'); + response.get('location').should.equal('http://example.com/cb?error=invalid_scope&error_description=Invalid%20parameter%3A%20%60scope%60&state=foobar'); }); }); it('should redirect to a successful response if `model.validateScope` is not defined', function() { - var client = { grants: ['authorization_code'], redirectUris: ['http://example.com/cb'] }; - var model = { + const client = { grants: ['authorization_code'], redirectUris: ['http://example.com/cb'] }; + const model = { getAccessToken: function() { return { client: client, @@ -349,8 +388,8 @@ describe('AuthorizeHandler integration', function() { return { authorizationCode: 12345, client: client }; } }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: { client_id: 12345, response_type: 'code' @@ -364,7 +403,7 @@ describe('AuthorizeHandler integration', function() { state: 'foobar' } }); - var response = new Response({ body: {}, headers: {} }); + const response = new Response({ body: {}, headers: {} }); return handler.handle(request, response) .then(function(data) { @@ -377,8 +416,8 @@ describe('AuthorizeHandler integration', function() { }); it('should redirect to an error response if `scope` is insufficient', function() { - var client = { grants: ['authorization_code'], redirectUris: ['http://example.com/cb'] }; - var model = { + const client = { grants: ['authorization_code'], redirectUris: ['http://example.com/cb'] }; + const model = { getAccessToken: function() { return { client: client, @@ -396,8 +435,8 @@ describe('AuthorizeHandler integration', function() { return false; } }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: { client_id: 12345, response_type: 'code' @@ -411,17 +450,17 @@ describe('AuthorizeHandler integration', function() { state: 'foobar' } }); - var response = new Response({ body: {}, headers: {} }); + const 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%20scope%3A%20Requested%20scope%20is%20invalid'); + response.get('location').should.equal('http://example.com/cb?error=invalid_scope&error_description=Invalid%20scope%3A%20Requested%20scope%20is%20invalid&state=foobar'); }); }); it('should redirect to an error response if `state` is missing', function() { - var model = { + const model = { getAccessToken: function() { return { user: {}, @@ -435,8 +474,8 @@ describe('AuthorizeHandler integration', function() { throw new AccessDeniedError('Cannot request this auth code'); } }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: { client_id: 12345, response_type: 'code' @@ -447,7 +486,7 @@ describe('AuthorizeHandler integration', function() { method: {}, query: {} }); - var response = new Response({ body: {}, headers: {} }); + const response = new Response({ body: {}, headers: {} }); return handler.handle(request, response) .then(should.fail) @@ -457,7 +496,7 @@ describe('AuthorizeHandler integration', function() { }); it('should redirect to an error response if `response_type` is invalid', function() { - var model = { + const model = { getAccessToken: function() { return { user: {}, @@ -471,8 +510,8 @@ describe('AuthorizeHandler integration', function() { return { authorizationCode: 12345, client: {} }; } }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: { client_id: 12345, response_type: 'test' @@ -485,7 +524,7 @@ describe('AuthorizeHandler integration', function() { state: 'foobar' } }); - var response = new Response({ body: {}, headers: {} }); + const response = new Response({ body: {}, headers: {} }); return handler.handle(request, response) .then(should.fail) @@ -495,7 +534,7 @@ describe('AuthorizeHandler integration', function() { }); it('should fail on invalid `response_type` before calling model.saveAuthorizationCode()', function() { - var model = { + const model = { getAccessToken: function() { return { user: {}, @@ -509,8 +548,8 @@ describe('AuthorizeHandler integration', function() { throw new Error('must not be reached'); } }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: { client_id: 12345, response_type: 'test' @@ -523,7 +562,7 @@ describe('AuthorizeHandler integration', function() { state: 'foobar' } }); - var response = new Response({ body: {}, headers: {} }); + const response = new Response({ body: {}, headers: {} }); return handler.handle(request, response) .then(should.fail) @@ -533,8 +572,8 @@ describe('AuthorizeHandler integration', function() { }); it('should return the `code` if successful', function() { - var client = { grants: ['authorization_code'], redirectUris: ['http://example.com/cb'] }; - var model = { + const client = { grants: ['authorization_code'], redirectUris: ['http://example.com/cb'] }; + const model = { getAccessToken: function() { return { client: client, @@ -549,8 +588,8 @@ describe('AuthorizeHandler integration', function() { return { authorizationCode: 12345, client: client }; } }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: { client_id: 12345, response_type: 'code' @@ -563,7 +602,7 @@ describe('AuthorizeHandler integration', function() { state: 'foobar' } }); - var response = new Response({ body: {}, headers: {} }); + const response = new Response({ body: {}, headers: {} }); return handler.handle(request, response) .then(function(data) { @@ -578,22 +617,22 @@ describe('AuthorizeHandler integration', function() { describe('generateAuthorizationCode()', function() { it('should return an auth code', function() { - var model = { + const model = { getAccessToken: function() {}, getClient: function() {}, saveAuthorizationCode: function() {} }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); return handler.generateAuthorizationCode() .then(function(data) { - data.should.be.a.sha1; + data.should.be.a.sha256(); }) .catch(should.fail); }); it('should support promises', function() { - var model = { + const model = { generateAuthorizationCode: function() { return Promise.resolve({}); }, @@ -601,13 +640,13 @@ describe('AuthorizeHandler integration', function() { getClient: function() {}, saveAuthorizationCode: function() {} }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); handler.generateAuthorizationCode().should.be.an.instanceOf(Promise); }); it('should support non-promises', function() { - var model = { + const model = { generateAuthorizationCode: function() { return {}; }, @@ -615,7 +654,7 @@ describe('AuthorizeHandler integration', function() { getClient: function() {}, saveAuthorizationCode: function() {} }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); handler.generateAuthorizationCode().should.be.an.instanceOf(Promise); }); @@ -623,26 +662,85 @@ describe('AuthorizeHandler integration', function() { describe('getAuthorizationCodeLifetime()', function() { it('should return a date', function() { - var model = { + const model = { getAccessToken: function() {}, getClient: function() {}, saveAuthorizationCode: function() {} }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); handler.getAuthorizationCodeLifetime().should.be.an.instanceOf(Date); }); }); + describe('validateRedirectUri()', function() { + it('should support empty method', function() { + const model = { + getAccessToken: function() {}, + getClient: function() {}, + saveAuthorizationCode: function() {} + }; + + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + + handler.validateRedirectUri('http://example.com/a', { redirectUris: ['http://example.com/a'] }).should.be.an.instanceOf(Promise); + }); + + it('should support promises', function() { + const model = { + getAccessToken: function() {}, + getClient: function() {}, + saveAuthorizationCode: function() {}, + validateRedirectUri: function() { + return Promise.resolve(true); + } + }; + + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + + handler.validateRedirectUri('http://example.com/a', { }).should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', function() { + const model = { + getAccessToken: function() {}, + getClient: function() {}, + saveAuthorizationCode: function() {}, + validateRedirectUri: function() { + return true; + } + }; + + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + + handler.validateRedirectUri('http://example.com/a', { }).should.be.an.instanceOf(Promise); + }); + + it('should support callbacks', function() { + const model = { + getAccessToken: function() {}, + getClient: function() {}, + saveAuthorizationCode: function() {}, + validateRedirectUri: function(redirectUri, client, callback) { + callback(null, false); + } + }; + + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + + handler.validateRedirectUri('http://example.com/a', { }).should.be.an.instanceOf(Promise); + }); + }); + describe('getClient()', function() { it('should throw an error if `client_id` is missing', function() { - var model = { + const 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: {} }); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: { response_type: 'code' }, headers: {}, method: {}, query: {} }); try { handler.getClient(request); @@ -655,13 +753,13 @@ describe('AuthorizeHandler integration', function() { }); it('should throw an error if `client_id` is invalid', function() { - var model = { + const 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: {} }); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: { client_id: 'ø倣‰', response_type: 'code' }, headers: {}, method: {}, query: {} }); try { handler.getClient(request); @@ -674,13 +772,13 @@ describe('AuthorizeHandler integration', function() { }); it('should throw an error if `client.redirectUri` is invalid', function() { - var model = { + const 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: {} }); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: { client_id: 12345, response_type: 'code', redirect_uri: 'foobar' }, headers: {}, method: {}, query: {} }); try { handler.getClient(request); @@ -693,13 +791,13 @@ describe('AuthorizeHandler integration', function() { }); it('should throw an error if `client` is missing', function() { - var model = { + const 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: {} }); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: { client_id: 12345, response_type: 'code' }, headers: {}, method: {}, query: {} }); return handler.getClient(request) .then(should.fail) @@ -710,15 +808,15 @@ describe('AuthorizeHandler integration', function() { }); it('should throw an error if `client.grants` is missing', function() { - var model = { + const 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: {} }); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: { client_id: 12345, response_type: 'code' }, headers: {}, method: {}, query: {} }); return handler.getClient(request) .then(should.fail) @@ -729,15 +827,15 @@ describe('AuthorizeHandler integration', function() { }); it('should throw an error if `client` is unauthorized', function() { - var model = { + const 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: {} }); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: { client_id: 12345, response_type: 'code' }, headers: {}, method: {}, query: {} }); return handler.getClient(request) .then(should.fail) @@ -748,13 +846,13 @@ describe('AuthorizeHandler integration', function() { }); it('should throw an error if `client.redirectUri` is missing', function() { - var model = { + const 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: {} }); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: { client_id: 12345, response_type: 'code' }, headers: {}, method: {}, query: {} }); return handler.getClient(request) .then(should.fail) @@ -765,15 +863,15 @@ describe('AuthorizeHandler integration', function() { }); it('should throw an error if `client.redirectUri` is not equal to `redirectUri`', function() { - var model = { + const 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: {} }); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: 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) @@ -784,15 +882,15 @@ describe('AuthorizeHandler integration', function() { }); it('should support promises', function() { - var model = { + const 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({ + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: { client_id: 12345 }, headers: {}, method: {}, @@ -803,15 +901,15 @@ describe('AuthorizeHandler integration', function() { }); it('should support non-promises', function() { - var model = { + const 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({ + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: { client_id: 12345 }, headers: {}, method: {}, @@ -822,7 +920,7 @@ describe('AuthorizeHandler integration', function() { }); it('should support callbacks', function() { - var model = { + const model = { getAccessToken: function() {}, getClient: function(clientId, clientSecret, callback) { should.equal(clientSecret, null); @@ -830,8 +928,8 @@ describe('AuthorizeHandler integration', function() { }, saveAuthorizationCode: function() {} }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: { client_id: 12345 }, headers: {}, method: {}, @@ -843,16 +941,16 @@ describe('AuthorizeHandler integration', function() { 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 = { + const client = { grants: ['authorization_code'], redirectUris: ['http://example.com/cb'] }; + const 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 } }); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: { response_type: 'code' }, headers: {}, method: {}, query: { client_id: 12345 } }); return handler.getClient(request) .then(function(data) { @@ -865,13 +963,13 @@ describe('AuthorizeHandler integration', function() { describe('getScope()', function() { it('should throw an error if `scope` is invalid', function() { - var model = { + const model = { getAccessToken: function() {}, getClient: function() {}, saveAuthorizationCode: function() {} }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ body: { scope: 'ø倣‰' }, headers: {}, method: {}, query: {} }); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: { scope: 'ø倣‰' }, headers: {}, method: {}, query: {} }); try { handler.getScope(request); @@ -885,13 +983,13 @@ describe('AuthorizeHandler integration', function() { describe('with `scope` in the request body', function() { it('should return the scope', function() { - var model = { + const 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: {} }); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: { scope: 'foo' }, headers: {}, method: {}, query: {} }); handler.getScope(request).should.equal('foo'); }); @@ -899,13 +997,13 @@ describe('AuthorizeHandler integration', function() { describe('with `scope` in the request query', function() { it('should return the scope', function() { - var model = { + const 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' } }); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: {}, headers: {}, method: {}, query: { scope: 'foo' } }); handler.getScope(request).should.equal('foo'); }); @@ -914,13 +1012,13 @@ describe('AuthorizeHandler integration', function() { describe('getState()', function() { it('should throw an error if `allowEmptyState` is false and `state` is missing', function() { - var model = { + const 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: {} }); + const handler = new AuthorizeHandler({ allowEmptyState: false, authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: {}, headers: {}, method: {}, query: {} }); try { handler.getState(request); @@ -932,14 +1030,26 @@ describe('AuthorizeHandler integration', function() { } }); + it('should allow missing `state` if `allowEmptyState` is valid', function () { + const model = { + getAccessToken: function() {}, + getClient: function() {}, + saveAuthorizationCode: function() {} + }; + const handler = new AuthorizeHandler({ allowEmptyState: true, authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: {}, headers: {}, method: {}, query: {} }); + const state = handler.getState(request); + should.equal(state, undefined); + }); + it('should throw an error if `state` is invalid', function() { - var model = { + const model = { getAccessToken: function() {}, getClient: function() {}, saveAuthorizationCode: function() {} }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: { state: 'ø倣‰' } }); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: {}, headers: {}, method: {}, query: { state: 'ø倣‰' } }); try { handler.getState(request); @@ -953,13 +1063,13 @@ describe('AuthorizeHandler integration', function() { describe('with `state` in the request body', function() { it('should return the state', function() { - var model = { + const 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: {} }); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: { state: 'foobar' }, headers: {}, method: {}, query: {} }); handler.getState(request).should.equal('foobar'); }); @@ -967,13 +1077,13 @@ describe('AuthorizeHandler integration', function() { describe('with `state` in the request query', function() { it('should return the state', function() { - var model = { + const 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' } }); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: {}, headers: {}, method: {}, query: { state: 'foobar' } }); handler.getState(request).should.equal('foobar'); }); @@ -982,14 +1092,14 @@ describe('AuthorizeHandler integration', function() { describe('getUser()', function() { it('should throw an error if `user` is missing', function() { - var authenticateHandler = { handle: function() {} }; - var model = { + const authenticateHandler = { handle: function() {} }; + const 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(); + const handler = new AuthorizeHandler({ authenticateHandler: authenticateHandler, authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: {}, headers: {}, method: {}, query: {} }); + const response = new Response(); return handler.getUser(request, response) .then(should.fail) @@ -1000,8 +1110,8 @@ describe('AuthorizeHandler integration', function() { }); it('should return a user', function() { - var user = {}; - var model = { + const user = {}; + const model = { getAccessToken: function() { return { user: user, @@ -1011,9 +1121,9 @@ describe('AuthorizeHandler integration', function() { 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: {} }); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: {}, headers: { 'Authorization': 'Bearer foo' }, method: {}, query: {} }); + const response = new Response({ body: {}, headers: {} }); return handler.getUser(request, response) .then(function(data) { @@ -1025,15 +1135,15 @@ describe('AuthorizeHandler integration', function() { describe('saveAuthorizationCode()', function() { it('should return an auth code', function() { - var authorizationCode = {}; - var model = { + const authorizationCode = {}; + const model = { getAccessToken: function() {}, getClient: function() {}, saveAuthorizationCode: function() { return authorizationCode; } }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); return handler.saveAuthorizationCode('foo', 'bar', 'biz', 'baz') .then(function(data) { @@ -1043,40 +1153,40 @@ describe('AuthorizeHandler integration', function() { }); it('should support promises when calling `model.saveAuthorizationCode()`', function() { - var model = { + const model = { getAccessToken: function() {}, getClient: function() {}, saveAuthorizationCode: function() { return Promise.resolve({}); } }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const 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 = { + const model = { getAccessToken: function() {}, getClient: function() {}, saveAuthorizationCode: function() { return {}; } }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const 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 = { + const model = { getAccessToken: function() {}, getClient: function() {}, saveAuthorizationCode: function(code, client, user, callback) { return callback(null, true); } }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); handler.saveAuthorizationCode('foo', 'bar', 'biz', 'baz').should.be.an.instanceOf(Promise); }); @@ -1084,13 +1194,13 @@ describe('AuthorizeHandler integration', function() { describe('getResponseType()', function() { it('should throw an error if `response_type` is missing', function() { - var model = { + const model = { getAccessToken: function() {}, getClient: function() {}, saveAuthorizationCode: function() {} }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: {}, headers: {}, method: {}, query: {} }); try { handler.getResponseType(request); @@ -1103,13 +1213,13 @@ describe('AuthorizeHandler integration', function() { }); it('should throw an error if `response_type` is not `code`', function() { - var model = { + const 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: {} }); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: { response_type: 'foobar' }, headers: {}, method: {}, query: {} }); try { handler.getResponseType(request); @@ -1123,14 +1233,14 @@ describe('AuthorizeHandler integration', function() { 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); + const model = { + getAccessToken: function() {}, + getClient: function() {}, + saveAuthorizationCode: function() {} + }; + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: { response_type: 'code' }, headers: {}, method: {}, query: {} }); + const ResponseType = handler.getResponseType(request); ResponseType.should.equal(CodeResponseType); }); @@ -1138,14 +1248,14 @@ describe('AuthorizeHandler integration', function() { 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); + const model = { + getAccessToken: function() {}, + getClient: function() {}, + saveAuthorizationCode: function() {} + }; + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: {}, headers: {}, method: {}, query: { response_type: 'code' } }); + const ResponseType = handler.getResponseType(request); ResponseType.should.equal(CodeResponseType); }); @@ -1154,14 +1264,14 @@ describe('AuthorizeHandler integration', function() { describe('buildSuccessRedirectUri()', function() { it('should return a redirect uri', function() { - var model = { + const 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); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: 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'); }); @@ -1169,27 +1279,27 @@ describe('AuthorizeHandler integration', function() { describe('buildErrorRedirectUri()', function() { it('should set `error_description` if available', function() { - var error = new InvalidClientError('foo bar'); - var model = { + const error = new InvalidClientError('foo bar'); + const model = { getAccessToken: function() {}, getClient: function() {}, saveAuthorizationCode: function() {} }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var redirectUri = handler.buildErrorRedirectUri('http://example.com/cb', error); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: 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', function() { - var error = new InvalidClientError(); - var model = { + const error = new InvalidClientError(); + const model = { getAccessToken: function() {}, getClient: function() {}, saveAuthorizationCode: function() {} }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var redirectUri = handler.buildErrorRedirectUri('http://example.com/cb', error); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: 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'); }); @@ -1197,14 +1307,14 @@ describe('AuthorizeHandler integration', function() { describe('updateResponse()', function() { it('should set the `location` header', function() { - var model = { + const 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'); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const response = new Response({ body: {}, headers: {} }); + const uri = url.parse('http://example.com/cb'); handler.updateResponse(response, uri, 'foobar'); diff --git a/test/integration/handlers/token-handler_test.js b/test/integration/handlers/token-handler_test.js index 50277c113..41ec5248b 100644 --- a/test/integration/handlers/token-handler_test.js +++ b/test/integration/handlers/token-handler_test.js @@ -4,22 +4,22 @@ * 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'); +const AccessDeniedError = require('../../../lib/errors/access-denied-error'); +const BearerTokenType = require('../../../lib/token-types/bearer-token-type'); +const InvalidArgumentError = require('../../../lib/errors/invalid-argument-error'); +const InvalidClientError = require('../../../lib/errors/invalid-client-error'); +const InvalidGrantError = require('../../../lib/errors/invalid-grant-error'); +const InvalidRequestError = require('../../../lib/errors/invalid-request-error'); +const PasswordGrantType = require('../../../lib/grant-types/password-grant-type'); +const Promise = require('bluebird'); +const Request = require('../../../lib/request'); +const Response = require('../../../lib/response'); +const ServerError = require('../../../lib/errors/server-error'); +const TokenHandler = require('../../../lib/handlers/token-handler'); +const UnauthorizedClientError = require('../../../lib/errors/unauthorized-client-error'); +const UnsupportedGrantTypeError = require('../../../lib/errors/unsupported-grant-type-error'); +const should = require('chai').should(); +const util = require('util'); /** * Test `TokenHandler` integration. @@ -72,76 +72,75 @@ describe('TokenHandler integration', function() { }); it('should set the `accessTokenLifetime`', function() { - var accessTokenLifetime = {}; - var model = { + const accessTokenLifetime = {}; + const model = { getClient: function() {}, saveToken: function() {} }; - var handler = new TokenHandler({ accessTokenLifetime: accessTokenLifetime, model: model, refreshTokenLifetime: 120 }); + const 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 = { + const alwaysIssueNewRefreshToken = true; + const model = { getClient: function() {}, saveToken: function() {} }; - var handler = new TokenHandler({ accessTokenLifetime: 123, model: model, refreshTokenLifetime: 120, alwaysIssueNewRefreshToken: alwaysIssueNewRefreshToken }); + const 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 = { + const alwaysIssueNewRefreshToken = false; + const model = { getClient: function() {}, saveToken: function() {} }; - var handler = new TokenHandler({ accessTokenLifetime: 123, model: model, refreshTokenLifetime: 120, alwaysIssueNewRefreshToken: alwaysIssueNewRefreshToken }); + const 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 = { + const model = { getClient: function() {}, saveToken: function() {} }; - var handler = new TokenHandler({ accessTokenLifetime: 123, model: model, refreshTokenLifetime: 120 }); + const 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 = { + const extendedGrantTypes = { foo: 'bar' }; + const model = { getClient: function() {}, saveToken: function() {} }; - var handler = new TokenHandler({ accessTokenLifetime: 120, extendedGrantTypes: extendedGrantTypes, model: model, refreshTokenLifetime: 120 }); - - handler.grantTypes.should.containEql(extendedGrantTypes); + const handler = new TokenHandler({ accessTokenLifetime: 120, extendedGrantTypes: extendedGrantTypes, model: model, refreshTokenLifetime: 120 }); + handler.grantTypes.should.deep.include(extendedGrantTypes); }); it('should set the `model`', function() { - var model = { + const model = { getClient: function() {}, saveToken: function() {} }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); handler.model.should.equal(model); }); it('should set the `refreshTokenLifetime`', function() { - var refreshTokenLifetime = {}; - var model = { + const refreshTokenLifetime = {}; + const model = { getClient: function() {}, saveToken: function() {} }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: refreshTokenLifetime }); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: refreshTokenLifetime }); handler.refreshTokenLifetime.should.equal(refreshTokenLifetime); }); @@ -149,11 +148,11 @@ describe('TokenHandler integration', function() { describe('handle()', function() { it('should throw an error if `request` is missing', function() { - var model = { + const model = { getClient: function() {}, saveToken: function() {} }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); try { handler.handle(); @@ -166,12 +165,12 @@ describe('TokenHandler integration', function() { }); it('should throw an error if `response` is missing', function() { - var model = { + const model = { getClient: function() {}, saveToken: function() {} }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const request = new Request({ body: {}, headers: {}, method: {}, query: {} }); try { handler.handle(request); @@ -184,13 +183,13 @@ describe('TokenHandler integration', function() { }); it('should throw an error if the method is not `POST`', function() { - var model = { + const 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: {} }); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: 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) @@ -201,13 +200,13 @@ describe('TokenHandler integration', function() { }); it('should throw an error if the media type is not `application/x-www-form-urlencoded`', function() { - var model = { + const 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: {} }); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: 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) @@ -218,13 +217,13 @@ describe('TokenHandler integration', function() { }); it('should throw the error if an oauth error is thrown', function() { - var model = { + const 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: {} }); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: 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) @@ -235,15 +234,15 @@ describe('TokenHandler integration', function() { }); it('should throw a server error if a non-oauth error is thrown', function() { - var model = { + const 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({ + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const request = new Request({ body: { client_id: 12345, client_secret: 'secret', @@ -255,7 +254,7 @@ describe('TokenHandler integration', function() { method: 'POST', query: {} }); - var response = new Response({ body: {}, headers: {} }); + const response = new Response({ body: {}, headers: {} }); return handler.handle(request, response) .then(should.fail) @@ -267,15 +266,15 @@ describe('TokenHandler integration', function() { }); it('should update the response if an error is thrown', function() { - var model = { + const 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({ + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const request = new Request({ body: { client_id: 12345, client_secret: 'secret', @@ -287,7 +286,7 @@ describe('TokenHandler integration', function() { method: 'POST', query: {} }); - var response = new Response({ body: {}, headers: {} }); + const response = new Response({ body: {}, headers: {} }); return handler.handle(request, response) .then(should.fail) @@ -298,15 +297,15 @@ describe('TokenHandler integration', function() { }); it('should return a bearer token if successful', function() { - var token = { accessToken: 'foo', client: {}, refreshToken: 'bar', scope: 'foobar', user: {} }; - var model = { + const token = { accessToken: 'foo', client: {}, refreshToken: 'bar', scope: 'foobar', user: {} }; + const 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({ + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const request = new Request({ body: { client_id: 12345, client_secret: 'secret', @@ -319,7 +318,7 @@ describe('TokenHandler integration', function() { method: 'POST', query: {} }); - var response = new Response({ body: {}, headers: {} }); + const response = new Response({ body: {}, headers: {} }); return handler.handle(request, response) .then(function(data) { @@ -329,15 +328,15 @@ describe('TokenHandler integration', function() { }); 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 = { + const token = { accessToken: 'foo', client: {}, refreshToken: 'bar', scope: 'foobar', user: {}, foo: 'bar' }; + const 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({ + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const request = new Request({ body: { client_id: 12345, client_secret: 'secret', @@ -350,7 +349,7 @@ describe('TokenHandler integration', function() { method: 'POST', query: {} }); - var response = new Response({ body: {}, headers: {} }); + const response = new Response({ body: {}, headers: {} }); return handler.handle(request, response) .then(function() { @@ -364,15 +363,15 @@ describe('TokenHandler integration', function() { }); 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 = { + const token = { accessToken: 'foo', client: {}, refreshToken: 'bar', scope: 'foobar', user: {}, foo: 'bar' }; + const 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({ + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120, allowExtendedTokenAttributes: true }); + const request = new Request({ body: { client_id: 12345, client_secret: 'secret', @@ -385,7 +384,7 @@ describe('TokenHandler integration', function() { method: 'POST', query: {} }); - var response = new Response({ body: {}, headers: {} }); + const response = new Response({ body: {}, headers: {} }); return handler.handle(request, response) .then(function() { @@ -402,12 +401,12 @@ describe('TokenHandler integration', function() { describe('getClient()', function() { it('should throw an error if `clientId` is invalid', function() { - var model = { + const 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: {} }); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const request = new Request({ body: { client_id: 'ø倣‰', client_secret: 'foo' }, headers: {}, method: {}, query: {} }); try { handler.getClient(request); @@ -420,12 +419,12 @@ describe('TokenHandler integration', function() { }); it('should throw an error if `clientSecret` is invalid', function() { - var model = { + const 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: {} }); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const request = new Request({ body: { client_id: 'foo', client_secret: 'ø倣‰' }, headers: {}, method: {}, query: {} }); try { handler.getClient(request); @@ -438,12 +437,12 @@ describe('TokenHandler integration', function() { }); it('should throw an error if `client` is missing', function() { - var model = { + const 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: {} }); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, method: {}, query: {} }); return handler.getClient(request) .then(should.fail) @@ -454,12 +453,12 @@ describe('TokenHandler integration', function() { }); it('should throw an error if `client.grants` is missing', function() { - var model = { + const 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: {} }); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, method: {}, query: {} }); return handler.getClient(request) .then(should.fail) @@ -470,12 +469,12 @@ describe('TokenHandler integration', function() { }); it('should throw an error if `client.grants` is invalid', function() { - var model = { + const 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: {} }); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, method: {}, query: {} }); return handler.getClient(request) .then(should.fail) @@ -486,18 +485,18 @@ describe('TokenHandler integration', function() { }); it('should throw a 401 error if the client is invalid and the request contains an authorization header', function() { - var model = { + const model = { getClient: function() {}, saveToken: function() {} }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const request = new Request({ body: {}, - headers: { 'authorization': util.format('Basic %s', new Buffer('foo:bar').toString('base64')) }, + headers: { 'authorization': util.format('Basic %s', Buffer.from('foo:bar').toString('base64')) }, method: {}, query: {} }); - var response = new Response({ body: {}, headers: {} }); + const response = new Response({ body: {}, headers: {} }); return handler.getClient(request, response) .then(should.fail) @@ -511,13 +510,13 @@ describe('TokenHandler integration', function() { }); it('should return a client', function() { - var client = { id: 12345, grants: [] }; - var model = { + const client = { id: 12345, grants: [] }; + const 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: {} }); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, method: {}, query: {} }); return handler.getClient(request) .then(function(data) { @@ -529,21 +528,21 @@ describe('TokenHandler integration', function() { describe('with `password` grant type and `requireClientAuthentication` is false', function() { it('should return a client ', function() { - var client = { id: 12345, grants: [] }; - var model = { + const client = { id: 12345, grants: [] }; + const model = { getClient: function() { return client; }, saveToken: function() {} }; - var handler = new TokenHandler({ + const 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: {} }); + }); + const request = new Request({ body: { client_id: 'blah', grant_type: 'password'}, headers: {}, method: {}, query: {} }); return handler.getClient(request) .then(function(data) { @@ -556,26 +555,26 @@ describe('TokenHandler integration', function() { 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 = { + const client = { id: 12345, grants: [] }; + const model = { getClient: function() { return client; }, saveToken: function() {} }; - var handler = new TokenHandler({ + const 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: {} - }); + }); + 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) .then(function(data) { @@ -586,34 +585,34 @@ describe('TokenHandler integration', function() { }); it('should support promises', function() { - var model = { + const 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: {} }); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + 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', function() { - var model = { + const 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: {} }); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + 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', function() { - var model = { + const 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: {} }); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, method: {}, query: {} }); handler.getClient(request).should.be.an.instanceOf(Promise); }); @@ -621,12 +620,12 @@ describe('TokenHandler integration', function() { describe('getClientCredentials()', function() { it('should throw an error if `client_id` is missing', function() { - var model = { + const 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: {} }); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const request = new Request({ body: { client_secret: 'foo' }, headers: {}, method: {}, query: {} }); try { handler.getClientCredentials(request); @@ -639,12 +638,12 @@ describe('TokenHandler integration', function() { }); it('should throw an error if `client_secret` is missing', function() { - var model = { + const 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: {} }); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const request = new Request({ body: { client_id: 'foo' }, headers: {}, method: {}, query: {} }); try { handler.getClientCredentials(request); @@ -658,13 +657,13 @@ describe('TokenHandler integration', function() { describe('with `client_id` and grant type is `password` and `requireClientAuthentication` is false', function() { it('should return a client', function() { - var model = { + const 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); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: 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' }); }); @@ -672,20 +671,20 @@ describe('TokenHandler integration', function() { describe('with `client_id` and `client_secret` in the request header as basic auth', function() { it('should return a client', function() { - var model = { + const model = { getClient: function() {}, saveToken: function() {} }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const request = new Request({ body: {}, headers: { - 'authorization': util.format('Basic %s', new Buffer('foo:bar').toString('base64')) + 'authorization': util.format('Basic %s', Buffer.from('foo:bar').toString('base64')) }, method: {}, query: {} }); - var credentials = handler.getClientCredentials(request); + const credentials = handler.getClientCredentials(request); credentials.should.eql({ clientId: 'foo', clientSecret: 'bar' }); }); @@ -693,13 +692,13 @@ describe('TokenHandler integration', function() { describe('with `client_id` and `client_secret` in the request body', function() { it('should return a client', function() { - var model = { + const 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); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: 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' }); }); @@ -708,12 +707,12 @@ describe('TokenHandler integration', function() { describe('handleGrantType()', function() { it('should throw an error if `grant_type` is missing', function() { - var model = { + const model = { getClient: function() {}, saveToken: function() {} }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const request = new Request({ body: {}, headers: {}, method: {}, query: {} }); try { handler.handleGrantType(request); @@ -726,12 +725,12 @@ describe('TokenHandler integration', function() { }); it('should throw an error if `grant_type` is invalid', function() { - var model = { + const 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: {} }); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const request = new Request({ body: { grant_type: '~foo~' }, headers: {}, method: {}, query: {} }); try { handler.handleGrantType(request); @@ -744,12 +743,12 @@ describe('TokenHandler integration', function() { }); it('should throw an error if `grant_type` is unsupported', function() { - var model = { + const 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: {} }); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const request = new Request({ body: { grant_type: 'foobar' }, headers: {}, method: {}, query: {} }); try { handler.handleGrantType(request); @@ -762,13 +761,13 @@ describe('TokenHandler integration', function() { }); it('should throw an error if `grant_type` is unauthorized', function() { - var client = { grants: ['client_credentials'] }; - var model = { + const client = { grants: ['client_credentials'] }; + const 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: {} }); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const request = new Request({ body: { grant_type: 'password' }, headers: {}, method: {}, query: {} }); try { handler.handleGrantType(request, client); @@ -781,14 +780,14 @@ describe('TokenHandler integration', function() { }); it('should throw an invalid grant error if a non-oauth error is thrown', function() { - var client = { grants: ['password'] }; - var model = { + const client = { grants: ['password'] }; + const 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: {} }); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: 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) @@ -800,17 +799,17 @@ describe('TokenHandler integration', function() { describe('with grant_type `authorization_code`', function() { it('should return a token', function() { - var client = { id: 'foobar', grants: ['authorization_code'] }; - var token = {}; - var model = { + const client = { id: 'foobar', grants: ['authorization_code'] }; + const token = {}; + const 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({ + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const request = new Request({ body: { code: 12345, grant_type: 'authorization_code' @@ -830,16 +829,16 @@ describe('TokenHandler integration', function() { describe('with grant_type `client_credentials`', function() { it('should return a token', function() { - var client = { grants: ['client_credentials'] }; - var token = {}; - var model = { + const client = { grants: ['client_credentials'] }; + const token = {}; + const 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({ + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const request = new Request({ body: { grant_type: 'client_credentials', scope: 'foo' @@ -859,16 +858,16 @@ describe('TokenHandler integration', function() { describe('with grant_type `password`', function() { it('should return a token', function() { - var client = { grants: ['password'] }; - var token = {}; - var model = { + const client = { grants: ['password'] }; + const token = {}; + const 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({ + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const request = new Request({ body: { client_id: 12345, client_secret: 'secret', @@ -892,16 +891,16 @@ describe('TokenHandler integration', function() { 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 = { + const client = { grants: ['refresh_token'] }; + const token = { accessToken: 'foo', client: {}, user: {} }; + const 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({ + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const request = new Request({ body: { grant_type: 'refresh_token', refresh_token: 12345 @@ -921,16 +920,16 @@ describe('TokenHandler integration', function() { 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 = { + const client = { grants: ['urn:ietf:params:oauth:grant-type:saml2-bearer'] }; + const token = {}; + const 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: {} }); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: 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(function(data) { @@ -943,23 +942,23 @@ describe('TokenHandler integration', function() { describe('getAccessTokenLifetime()', function() { it('should return the client access token lifetime', function() { - var client = { accessTokenLifetime: 60 }; - var model = { + const client = { accessTokenLifetime: 60 }; + const model = { getClient: function() { return client; }, saveToken: function() {} }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const 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 = { + const client = {}; + const model = { getClient: function() { return client; }, saveToken: function() {} }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); handler.getAccessTokenLifetime(client).should.equal(120); }); @@ -967,23 +966,23 @@ describe('TokenHandler integration', function() { describe('getRefreshTokenLifetime()', function() { it('should return the client access token lifetime', function() { - var client = { refreshTokenLifetime: 60 }; - var model = { + const client = { refreshTokenLifetime: 60 }; + const model = { getClient: function() { return client; }, saveToken: function() {} }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const 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 = { + const client = {}; + const model = { getClient: function() { return client; }, saveToken: function() {} }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); handler.getRefreshTokenLifetime(client).should.equal(120); }); @@ -991,26 +990,25 @@ describe('TokenHandler integration', function() { describe('getTokenType()', function() { it('should return a token type', function() { - var model = { + const 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' }); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const tokenType = handler.getTokenType({ accessToken: 'foo', refreshToken: 'bar', scope: 'foobar' }); + tokenType.should.deep.include({ accessToken: 'foo', accessTokenLifetime: undefined, refreshToken: 'bar', scope: 'foobar' }); }); }); describe('updateSuccessResponse()', function() { it('should set the `body`', function() { - var model = { + const 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: {} }); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const tokenType = new BearerTokenType('foo', 'bar', 'biz'); + const response = new Response({ body: {}, headers: {} }); handler.updateSuccessResponse(response, tokenType); @@ -1018,13 +1016,13 @@ describe('TokenHandler integration', function() { }); it('should set the `Cache-Control` header', function() { - var model = { + const 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: {} }); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const tokenType = new BearerTokenType('foo', 'bar', 'biz'); + const response = new Response({ body: {}, headers: {} }); handler.updateSuccessResponse(response, tokenType); @@ -1032,13 +1030,13 @@ describe('TokenHandler integration', function() { }); it('should set the `Pragma` header', function() { - var model = { + const 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: {} }); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const tokenType = new BearerTokenType('foo', 'bar', 'biz'); + const response = new Response({ body: {}, headers: {} }); handler.updateSuccessResponse(response, tokenType); @@ -1048,13 +1046,13 @@ describe('TokenHandler integration', function() { describe('updateErrorResponse()', function() { it('should set the `body`', function() { - var error = new AccessDeniedError('Cannot request a token'); - var model = { + const error = new AccessDeniedError('Cannot request a token'); + const model = { getClient: function() {}, saveToken: function() {} }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var response = new Response({ body: {}, headers: {} }); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const response = new Response({ body: {}, headers: {} }); handler.updateErrorResponse(response, error); @@ -1063,13 +1061,13 @@ describe('TokenHandler integration', function() { }); it('should set the `status`', function() { - var error = new AccessDeniedError('Cannot request a token'); - var model = { + const error = new AccessDeniedError('Cannot request a token'); + const model = { getClient: function() {}, saveToken: function() {} }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var response = new Response({ body: {}, headers: {} }); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const response = new Response({ body: {}, headers: {} }); handler.updateErrorResponse(response, error); diff --git a/test/integration/request_test.js b/test/integration/request_test.js index a43527671..e0c98f131 100644 --- a/test/integration/request_test.js +++ b/test/integration/request_test.js @@ -4,9 +4,9 @@ * Module dependencies. */ -var Request = require('../../lib/request'); -var InvalidArgumentError = require('../../lib/errors/invalid-argument-error'); -var should = require('should'); +const Request = require('../../lib/request'); +const InvalidArgumentError = require('../../lib/errors/invalid-argument-error'); +const should = require('chai').should(); /** * Test `Request` integration. @@ -48,25 +48,25 @@ describe('Request integration', function() { }); it('should set the `body`', function() { - var request = new Request({ body: 'foo', headers: {}, method: {}, query: {} }); + const 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: {} }); + 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`', function() { - var request = new Request({ body: {}, headers: {}, method: 'biz', query: {} }); + const 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' }); + const request = new Request({ body: {}, headers: {}, method: {}, query: 'baz' }); request.query.should.equal('baz'); }); @@ -74,13 +74,13 @@ describe('Request integration', function() { describe('get()', function() { it('should return `undefined` if the field does not exist', function() { - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); + const 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({ + const request = new Request({ body: {}, headers: { 'content-type': 'text/html; charset=utf-8' @@ -95,7 +95,7 @@ describe('Request integration', function() { describe('is()', function() { it('should accept an array of `types`', function() { - var request = new Request({ + const request = new Request({ body: {}, headers: { 'content-type': 'application/json', @@ -109,7 +109,7 @@ describe('Request integration', function() { }); it('should accept multiple `types` as arguments', function() { - var request = new Request({ + const request = new Request({ body: {}, headers: { 'content-type': 'application/json', @@ -123,7 +123,7 @@ describe('Request integration', function() { }); it('should return the first matching type', function() { - var request = new Request({ + const request = new Request({ body: {}, headers: { 'content-type': 'text/html; charset=utf-8', @@ -137,7 +137,7 @@ describe('Request integration', function() { }); it('should return `false` if none of the `types` match', function() { - var request = new Request({ + const request = new Request({ body: {}, headers: { 'content-type': 'text/html; charset=utf-8', @@ -151,7 +151,7 @@ describe('Request integration', function() { }); it('should return `false` if the request has no body', function() { - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); + const request = new Request({ body: {}, headers: {}, method: {}, query: {} }); request.is('text/html').should.be.false; }); diff --git a/test/integration/response-types/code-response-type_test.js b/test/integration/response-types/code-response-type_test.js index 5461b62c4..44bd53b0f 100644 --- a/test/integration/response-types/code-response-type_test.js +++ b/test/integration/response-types/code-response-type_test.js @@ -4,10 +4,10 @@ * 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'); +const CodeResponseType = require('../../../lib/response-types/code-response-type'); +const InvalidArgumentError = require('../../../lib/errors/invalid-argument-error'); +const should = require('chai').should(); +const url = require('url'); /** * Test `CodeResponseType` integration. @@ -27,7 +27,7 @@ describe('CodeResponseType integration', function() { }); it('should set the `code`', function() { - var responseType = new CodeResponseType('foo'); + const responseType = new CodeResponseType('foo'); responseType.code.should.equal('foo'); }); @@ -35,7 +35,7 @@ describe('CodeResponseType integration', function() { describe('buildRedirectUri()', function() { it('should throw an error if the `redirectUri` is missing', function() { - var responseType = new CodeResponseType('foo'); + const responseType = new CodeResponseType('foo'); try { responseType.buildRedirectUri(); @@ -48,15 +48,15 @@ describe('CodeResponseType integration', function() { }); 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'); + const responseType = new CodeResponseType('foo'); + 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', function() { - var responseType = new CodeResponseType('foo'); - var redirectUri = responseType.buildRedirectUri('http://example.com/cb?foo=bar'); + const responseType = new CodeResponseType('foo'); + 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_test.js b/test/integration/response_test.js index 1e1e0206a..d6c37e43c 100644 --- a/test/integration/response_test.js +++ b/test/integration/response_test.js @@ -4,7 +4,7 @@ * Module dependencies. */ -var Response = require('../../lib/response'); +const Response = require('../../lib/response'); /** * Test `Response` integration. @@ -13,19 +13,19 @@ var Response = require('../../lib/response'); describe('Response integration', function() { describe('constructor()', function() { it('should set the `body`', function() { - var response = new Response({ body: 'foo', headers: {} }); + const 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' } }); + 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', function() { - var response = new Response({ body: {}, headers: {} }); + const response = new Response({ body: {}, headers: {} }); response.status.should.equal(200); }); @@ -33,13 +33,13 @@ describe('Response integration', function() { describe('get()', function() { it('should return `undefined` if the field does not exist', function() { - var response = new Response({ body: {}, headers: {} }); + const 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' } }); + const response = new Response({ body: {}, headers: { 'content-type': 'text/html; charset=utf-8' } }); response.get('Content-Type').should.equal('text/html; charset=utf-8'); }); @@ -47,7 +47,7 @@ describe('Response integration', function() { describe('redirect()', function() { it('should set the location header to `url`', function() { - var response = new Response({ body: {}, headers: {} }); + const response = new Response({ body: {}, headers: {} }); response.redirect('http://example.com'); @@ -55,7 +55,7 @@ describe('Response integration', function() { }); it('should set the `status` to 302', function() { - var response = new Response({ body: {}, headers: {} }); + const response = new Response({ body: {}, headers: {} }); response.redirect('http://example.com'); @@ -65,7 +65,7 @@ describe('Response integration', function() { describe('set()', function() { it('should set the `field`', function() { - var response = new Response({ body: {}, headers: {} }); + const response = new Response({ body: {}, headers: {} }); response.set('foo', 'bar'); diff --git a/test/integration/server_test.js b/test/integration/server_test.js index 2d3aa7845..db1054441 100644 --- a/test/integration/server_test.js +++ b/test/integration/server_test.js @@ -4,12 +4,12 @@ * 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'); +const InvalidArgumentError = require('../../lib/errors/invalid-argument-error'); +const Promise = require('bluebird'); +const Request = require('../../lib/request'); +const Response = require('../../lib/response'); +const Server = require('../../lib/server'); +const should = require('chai').should(); /** * Test `Server` integration. @@ -29,8 +29,8 @@ describe('Server integration', function() { }); it('should set the `model`', function() { - var model = {}; - var server = new Server({ model: model }); + const model = {}; + const server = new Server({ model: model }); server.options.model.should.equal(model); }); @@ -38,7 +38,7 @@ describe('Server integration', function() { describe('authenticate()', function() { it('should set the default `options`', function() { - var model = { + const model = { getAccessToken: function() { return { user: {}, @@ -46,9 +46,9 @@ describe('Server integration', function() { }; } }; - var server = new Server({ model: model }); - var request = new Request({ body: {}, headers: { 'Authorization': 'Bearer foo' }, method: {}, query: {} }); - var response = new Response({ body: {}, headers: {} }); + const server = new Server({ model: model }); + const request = new Request({ body: {}, headers: { 'Authorization': 'Bearer foo' }, method: {}, query: {} }); + const response = new Response({ body: {}, headers: {} }); return server.authenticate(request, response) .then(function() { @@ -60,7 +60,7 @@ describe('Server integration', function() { }); it('should return a promise', function() { - var model = { + const model = { getAccessToken: function(token, callback) { callback(null, { user: {}, @@ -68,16 +68,16 @@ describe('Server integration', function() { }); } }; - 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); + const server = new Server({ model: 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', function(next) { - var model = { + const model = { getAccessToken: function() { return { user: {}, @@ -85,9 +85,9 @@ describe('Server integration', function() { }; } }; - var server = new Server({ model: model }); - var request = new Request({ body: {}, headers: { 'Authorization': 'Bearer foo' }, method: {}, query: {} }); - var response = new Response({ body: {}, headers: {} }); + const server = new Server({ model: model }); + const request = new Request({ body: {}, headers: { 'Authorization': 'Bearer foo' }, method: {}, query: {} }); + const response = new Response({ body: {}, headers: {} }); server.authenticate(request, response, null, next); }); @@ -95,7 +95,7 @@ describe('Server integration', function() { describe('authorize()', function() { it('should set the default `options`', function() { - var model = { + const model = { getAccessToken: function() { return { user: {}, @@ -109,9 +109,9 @@ describe('Server integration', 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: {} }); + const server = new Server({ model: 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: {} }); return server.authorize(request, response) .then(function() { @@ -122,7 +122,7 @@ describe('Server integration', function() { }); it('should return a promise', function() { - var model = { + const model = { getAccessToken: function() { return { user: {}, @@ -136,16 +136,16 @@ describe('Server integration', 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); + const server = new Server({ model: 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: {} }); + const handler = server.authorize(request, response); handler.should.be.an.instanceOf(Promise); }); it('should support callbacks', function(next) { - var model = { + const model = { getAccessToken: function() { return { user: {}, @@ -159,9 +159,9 @@ describe('Server integration', 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: {} }); + const server = new Server({ model: 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: {} }); server.authorize(request, response, null, next); }); @@ -169,7 +169,7 @@ describe('Server integration', function() { describe('token()', function() { it('should set the default `options`', function() { - var model = { + const model = { getClient: function() { return { grants: ['password'] }; }, @@ -181,9 +181,9 @@ describe('Server integration', function() { }, 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: {} }); + const server = new Server({ model: 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: {} }); return server.token(request, response) .then(function() { @@ -194,7 +194,7 @@ describe('Server integration', function() { }); it('should return a promise', function() { - var model = { + const model = { getClient: function() { return { grants: ['password'] }; }, @@ -205,16 +205,16 @@ describe('Server integration', 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); + const server = new Server({ model: 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: {} }); + const handler = server.token(request, response); handler.should.be.an.instanceOf(Promise); }); it('should support callbacks', function(next) { - var model = { + const model = { getClient: function() { return { grants: ['password'] }; }, @@ -225,12 +225,12 @@ describe('Server integration', function() { return { accessToken: 1234, client: {}, user: {} }; }, validateScope: function() { - return 'foo'; + 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: {} }); + const server = new Server({ model: 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/token-types/bearer-token-type_test.js b/test/integration/token-types/bearer-token-type_test.js index 3c1ef6bdd..47b1daa6b 100644 --- a/test/integration/token-types/bearer-token-type_test.js +++ b/test/integration/token-types/bearer-token-type_test.js @@ -4,9 +4,9 @@ * Module dependencies. */ -var BearerTokenType = require('../../../lib/token-types/bearer-token-type'); -var InvalidArgumentError = require('../../../lib/errors/invalid-argument-error'); -var should = require('should'); +const BearerTokenType = require('../../../lib/token-types/bearer-token-type'); +const InvalidArgumentError = require('../../../lib/errors/invalid-argument-error'); +const should = require('chai').should(); /** * Test `BearerTokenType` integration. @@ -26,19 +26,19 @@ describe('BearerTokenType integration', function() { }); it('should set the `accessToken`', function() { - var responseType = new BearerTokenType('foo', 'bar'); + const responseType = new BearerTokenType('foo', 'bar'); responseType.accessToken.should.equal('foo'); }); it('should set the `accessTokenLifetime`', function() { - var responseType = new BearerTokenType('foo', 'bar'); + const responseType = new BearerTokenType('foo', 'bar'); responseType.accessTokenLifetime.should.equal('bar'); }); it('should set the `refreshToken`', function() { - var responseType = new BearerTokenType('foo', 'bar', 'biz'); + const responseType = new BearerTokenType('foo', 'bar', 'biz'); responseType.refreshToken.should.equal('biz'); }); @@ -46,8 +46,8 @@ describe('BearerTokenType integration', function() { describe('valueOf()', function() { it('should return the value representation', function() { - var responseType = new BearerTokenType('foo', 'bar'); - var value = responseType.valueOf(); + const responseType = new BearerTokenType('foo', 'bar'); + const value = responseType.valueOf(); value.should.eql({ access_token: 'foo', @@ -57,8 +57,8 @@ describe('BearerTokenType integration', function() { }); it('should not include the `expires_in` if not given', function() { - var responseType = new BearerTokenType('foo'); - var value = responseType.valueOf(); + const responseType = new BearerTokenType('foo'); + const value = responseType.valueOf(); value.should.eql({ access_token: 'foo', @@ -67,8 +67,8 @@ describe('BearerTokenType integration', function() { }); it('should set `refresh_token` if `refreshToken` is defined', function() { - var responseType = new BearerTokenType('foo', 'bar', 'biz'); - var value = responseType.valueOf(); + const responseType = new BearerTokenType('foo', 'bar', 'biz'); + const value = responseType.valueOf(); value.should.eql({ access_token: 'foo', @@ -79,8 +79,8 @@ describe('BearerTokenType integration', function() { }); it('should set `expires_in` if `accessTokenLifetime` is defined', function() { - var responseType = new BearerTokenType('foo', 'bar', 'biz'); - var value = responseType.valueOf(); + const responseType = new BearerTokenType('foo', 'bar', 'biz'); + const value = responseType.valueOf(); value.should.eql({ access_token: 'foo', diff --git a/test/integration/utils/token-util_test.js b/test/integration/utils/token-util_test.js index 3fbca3f65..d4f368eaa 100644 --- a/test/integration/utils/token-util_test.js +++ b/test/integration/utils/token-util_test.js @@ -4,8 +4,8 @@ * Module dependencies. */ -var TokenUtil = require('../../../lib/utils/token-util'); -var should = require('should'); +const TokenUtil = require('../../../lib/utils/token-util'); +const should = require('chai').should(); /** * Test `TokenUtil` integration. @@ -13,10 +13,10 @@ var should = require('should'); describe('TokenUtil integration', function() { describe('generateRandomToken()', function() { - it('should return a sha-1 token', function() { + it('should return a sha-256 token', function() { return TokenUtil.generateRandomToken() .then(function(token) { - token.should.be.a.sha1; + token.should.be.a.sha256(); }) .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/unit/grant-types/abstract-grant-type_test.js b/test/unit/grant-types/abstract-grant-type_test.js index 528ca4041..f6cb13a43 100644 --- a/test/unit/grant-types/abstract-grant-type_test.js +++ b/test/unit/grant-types/abstract-grant-type_test.js @@ -4,9 +4,9 @@ * Module dependencies. */ -var AbstractGrantType = require('../../../lib/grant-types/abstract-grant-type'); -var sinon = require('sinon'); -var should = require('should'); +const AbstractGrantType = require('../../../lib/grant-types/abstract-grant-type'); +const sinon = require('sinon'); +const should = require('chai').should(); /** * Test `AbstractGrantType`. @@ -15,10 +15,10 @@ var should = require('should'); describe('AbstractGrantType', function() { describe('generateAccessToken()', function() { it('should call `model.generateAccessToken()`', function() { - var model = { + const model = { generateAccessToken: sinon.stub().returns({ client: {}, expiresAt: new Date(), user: {} }) }; - var handler = new AbstractGrantType({ accessTokenLifetime: 120, model: model }); + const handler = new AbstractGrantType({ accessTokenLifetime: 120, model: model }); return handler.generateAccessToken() .then(function() { @@ -31,10 +31,10 @@ describe('AbstractGrantType', function() { describe('generateRefreshToken()', function() { it('should call `model.generateRefreshToken()`', function() { - var model = { + const model = { generateRefreshToken: sinon.stub().returns({ client: {}, expiresAt: new Date(new Date() / 2), user: {} }) }; - var handler = new AbstractGrantType({ accessTokenLifetime: 120, model: model }); + const handler = new AbstractGrantType({ accessTokenLifetime: 120, model: model }); return handler.generateRefreshToken() .then(function() { diff --git a/test/unit/grant-types/authorization-code-grant-type_test.js b/test/unit/grant-types/authorization-code-grant-type_test.js index 480416e68..83cc85437 100644 --- a/test/unit/grant-types/authorization-code-grant-type_test.js +++ b/test/unit/grant-types/authorization-code-grant-type_test.js @@ -4,11 +4,11 @@ * 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'); +const AuthorizationCodeGrantType = require('../../../lib/grant-types/authorization-code-grant-type'); +const Promise = require('bluebird'); +const Request = require('../../../lib/request'); +const sinon = require('sinon'); +const should = require('chai').should(); /** * Test `AuthorizationCodeGrantType`. @@ -17,14 +17,14 @@ var should = require('should'); describe('AuthorizationCodeGrantType', function() { describe('getAuthorizationCode()', function() { it('should call `model.getAuthorizationCode()`', function() { - var model = { + const 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 = {}; + const handler = new AuthorizationCodeGrantType({ accessTokenLifetime: 120, model: model }); + const request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); + const client = {}; return handler.getAuthorizationCode(request, client) .then(function() { @@ -39,13 +39,13 @@ describe('AuthorizationCodeGrantType', function() { describe('revokeAuthorizationCode()', function() { it('should call `model.revokeAuthorizationCode()`', function() { - var model = { + const model = { getAuthorizationCode: function() {}, revokeAuthorizationCode: sinon.stub().returns(true), saveToken: function() {} }; - var handler = new AuthorizationCodeGrantType({ accessTokenLifetime: 120, model: model }); - var authorizationCode = {}; + const handler = new AuthorizationCodeGrantType({ accessTokenLifetime: 120, model: model }); + const authorizationCode = {}; return handler.revokeAuthorizationCode(authorizationCode) .then(function() { @@ -60,14 +60,14 @@ describe('AuthorizationCodeGrantType', function() { describe('saveToken()', function() { it('should call `model.saveToken()`', function() { - var client = {}; - var user = {}; - var model = { + const client = {}; + const user = {}; + const model = { getAuthorizationCode: function() {}, revokeAuthorizationCode: function() {}, saveToken: sinon.stub().returns(true) }; - var handler = new AuthorizationCodeGrantType({ accessTokenLifetime: 120, model: model }); + const handler = new AuthorizationCodeGrantType({ accessTokenLifetime: 120, model: model }); sinon.stub(handler, 'validateScope').returns('foobiz'); sinon.stub(handler, 'generateAccessToken').returns(Promise.resolve('foo')); diff --git a/test/unit/grant-types/client-credentials-grant-type_test.js b/test/unit/grant-types/client-credentials-grant-type_test.js index fe1fc4840..3997823b0 100644 --- a/test/unit/grant-types/client-credentials-grant-type_test.js +++ b/test/unit/grant-types/client-credentials-grant-type_test.js @@ -4,9 +4,9 @@ * Module dependencies. */ -var ClientCredentialsGrantType = require('../../../lib/grant-types/client-credentials-grant-type'); -var sinon = require('sinon'); -var should = require('should'); +const ClientCredentialsGrantType = require('../../../lib/grant-types/client-credentials-grant-type'); +const sinon = require('sinon'); +const should = require('chai').should(); /** * Test `ClientCredentialsGrantType`. @@ -15,12 +15,12 @@ var should = require('should'); describe('ClientCredentialsGrantType', function() { describe('getUserFromClient()', function() { it('should call `model.getUserFromClient()`', function() { - var model = { + const model = { getUserFromClient: sinon.stub().returns(true), saveToken: function() {} }; - var handler = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); - var client = {}; + const handler = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); + const client = {}; return handler.getUserFromClient(client) .then(function() { @@ -35,13 +35,13 @@ describe('ClientCredentialsGrantType', function() { describe('saveToken()', function() { it('should call `model.saveToken()`', function() { - var client = {}; - var user = {}; - var model = { + const client = {}; + const user = {}; + const model = { getUserFromClient: function() {}, saveToken: sinon.stub().returns(true) }; - var handler = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); + const handler = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); sinon.stub(handler, 'validateScope').returns('foobar'); sinon.stub(handler, 'generateAccessToken').returns('foo'); diff --git a/test/unit/grant-types/password-grant-type_test.js b/test/unit/grant-types/password-grant-type_test.js index 8e3bfc84e..ceb2ad9d1 100644 --- a/test/unit/grant-types/password-grant-type_test.js +++ b/test/unit/grant-types/password-grant-type_test.js @@ -4,10 +4,10 @@ * Module dependencies. */ -var PasswordGrantType = require('../../../lib/grant-types/password-grant-type'); -var Request = require('../../../lib/request'); -var sinon = require('sinon'); -var should = require('should'); +const PasswordGrantType = require('../../../lib/grant-types/password-grant-type'); +const Request = require('../../../lib/request'); +const sinon = require('sinon'); +const should = require('chai').should(); /** * Test `PasswordGrantType`. @@ -16,12 +16,12 @@ var should = require('should'); describe('PasswordGrantType', function() { describe('getUser()', function() { it('should call `model.getUser()`', function() { - var model = { + const 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: {} }); + const handler = new PasswordGrantType({ accessTokenLifetime: 120, model: model }); + const request = new Request({ body: { username: 'foo', password: 'bar' }, headers: {}, method: {}, query: {} }); return handler.getUser(request) .then(function() { @@ -37,13 +37,13 @@ describe('PasswordGrantType', function() { describe('saveToken()', function() { it('should call `model.saveToken()`', function() { - var client = {}; - var user = {}; - var model = { + const client = {}; + const user = {}; + const model = { getUser: function() {}, saveToken: sinon.stub().returns(true) }; - var handler = new PasswordGrantType({ accessTokenLifetime: 120, model: model }); + const handler = new PasswordGrantType({ accessTokenLifetime: 120, model: model }); sinon.stub(handler, 'validateScope').returns('foobar'); sinon.stub(handler, 'generateAccessToken').returns('foo'); diff --git a/test/unit/grant-types/refresh-token-grant-type_test.js b/test/unit/grant-types/refresh-token-grant-type_test.js index e5693ba8f..c91a37ed2 100644 --- a/test/unit/grant-types/refresh-token-grant-type_test.js +++ b/test/unit/grant-types/refresh-token-grant-type_test.js @@ -4,10 +4,10 @@ * 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'); +const RefreshTokenGrantType = require('../../../lib/grant-types/refresh-token-grant-type'); +const Request = require('../../../lib/request'); +const sinon = require('sinon'); +const should = require('chai').should(); /** * Test `RefreshTokenGrantType`. @@ -16,15 +16,15 @@ var should = require('should'); describe('RefreshTokenGrantType', function() { describe('handle()', function() { it('should revoke the previous token', function() { - var token = { accessToken: 'foo', client: {}, user: {} }; - var model = { + const token = { accessToken: 'foo', client: {}, user: {} }; + const 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 = {}; + const handler = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); + const request = new Request({ body: { refresh_token: 'bar' }, headers: {}, method: {}, query: {} }); + const client = {}; return handler.handle(request, client) .then(function() { @@ -39,14 +39,14 @@ describe('RefreshTokenGrantType', function() { describe('getRefreshToken()', function() { it('should call `model.getRefreshToken()`', function() { - var model = { + const 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 = {}; + const handler = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); + const request = new Request({ body: { refresh_token: 'bar' }, headers: {}, method: {}, query: {} }); + const client = {}; return handler.getRefreshToken(request, client) .then(function() { @@ -61,13 +61,13 @@ describe('RefreshTokenGrantType', function() { describe('revokeToken()', function() { it('should call `model.revokeToken()`', function() { - var model = { + const 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 = {}; + const handler = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); + const token = {}; return handler.revokeToken(token) .then(function() { @@ -80,13 +80,13 @@ describe('RefreshTokenGrantType', function() { }); it('should not call `model.revokeToken()`', function() { - var model = { + const 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 = {}; + const handler = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model, alwaysIssueNewRefreshToken: false }); + const token = {}; return handler.revokeToken(token) .then(function() { @@ -96,13 +96,13 @@ describe('RefreshTokenGrantType', function() { }); it('should not call `model.revokeToken()`', function() { - var model = { + const 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 = {}; + const handler = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model, alwaysIssueNewRefreshToken: true }); + const token = {}; return handler.revokeToken(token) .then(function() { @@ -117,14 +117,14 @@ describe('RefreshTokenGrantType', function() { describe('saveToken()', function() { it('should call `model.saveToken()`', function() { - var client = {}; - var user = {}; - var model = { + const client = {}; + const user = {}; + const model = { getRefreshToken: function() {}, revokeToken: function() {}, saveToken: sinon.stub().returns(true) }; - var handler = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); + const handler = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); sinon.stub(handler, 'generateAccessToken').returns('foo'); sinon.stub(handler, 'generateRefreshToken').returns('bar'); @@ -144,14 +144,14 @@ describe('RefreshTokenGrantType', function() { }); it('should call `model.saveToken()` without refresh token', function() { - var client = {}; - var user = {}; - var model = { + const client = {}; + const user = {}; + const model = { getRefreshToken: function() {}, revokeToken: function() {}, saveToken: sinon.stub().returns(true) }; - var handler = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model, alwaysIssueNewRefreshToken: false }); + const handler = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model, alwaysIssueNewRefreshToken: false }); sinon.stub(handler, 'generateAccessToken').returns('foo'); sinon.stub(handler, 'generateRefreshToken').returns('bar'); @@ -171,14 +171,14 @@ describe('RefreshTokenGrantType', function() { }); it('should call `model.saveToken()` with refresh token', function() { - var client = {}; - var user = {}; - var model = { + const client = {}; + const user = {}; + const model = { getRefreshToken: function() {}, revokeToken: function() {}, saveToken: sinon.stub().returns(true) }; - var handler = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model, alwaysIssueNewRefreshToken: true}); + const handler = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model, alwaysIssueNewRefreshToken: true}); sinon.stub(handler, 'generateAccessToken').returns('foo'); sinon.stub(handler, 'generateRefreshToken').returns('bar'); diff --git a/test/unit/handlers/authenticate-handler_test.js b/test/unit/handlers/authenticate-handler_test.js index 2adac7884..ff0a924dd 100644 --- a/test/unit/handlers/authenticate-handler_test.js +++ b/test/unit/handlers/authenticate-handler_test.js @@ -4,11 +4,12 @@ * 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'); +const AuthenticateHandler = require('../../../lib/handlers/authenticate-handler'); +const InvalidRequestError = require('../../../lib/errors/invalid-request-error'); +const Request = require('../../../lib/request'); +const sinon = require('sinon'); +const should = require('chai').should(); +const ServerError = require('../../../lib/errors/server-error'); /** * Test `AuthenticateHandler`. @@ -16,10 +17,37 @@ var ServerError = require('../../../lib/errors/server-error'); describe('AuthenticateHandler', function() { describe('getTokenFromRequest()', function() { + describe('with bearer token in the request authorization header', function() { + it('should throw an error if the token is malformed', () => { + const handler = new AuthenticateHandler({ + model: { getAccessToken() {} }, + }); + const request = new Request({ + body: {}, + headers: { + Authorization: 'foo Bearer bar', + }, + method: 'ANY', + 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', + ); + } + }); + }); + 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({ + const handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); + const request = new Request({ body: {}, headers: { 'Authorization': 'Bearer foo' }, method: {}, @@ -38,8 +66,8 @@ describe('AuthenticateHandler', function() { 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({ + const handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); + const request = new Request({ body: {}, headers: {}, method: {}, @@ -58,8 +86,8 @@ describe('AuthenticateHandler', function() { 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({ + const handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); + const request = new Request({ body: { access_token: 'foo' }, headers: {}, method: {}, @@ -79,10 +107,10 @@ describe('AuthenticateHandler', function() { describe('getAccessToken()', function() { it('should call `model.getAccessToken()`', function() { - var model = { + const model = { getAccessToken: sinon.stub().returns({ user: {} }) }; - var handler = new AuthenticateHandler({ model: model }); + const handler = new AuthenticateHandler({ model: model }); return handler.getAccessToken('foo') .then(function() { @@ -97,12 +125,12 @@ describe('AuthenticateHandler', function() { describe('validateAccessToken()', function() { it('should fail if token has no valid `accessTokenExpiresAt` date', function() { - var model = { + const model = { getAccessToken: function() {} }; - var handler = new AuthenticateHandler({ model: model }); + const handler = new AuthenticateHandler({ model: model }); - var failed = false; + let failed = false; try { handler.validateAccessToken({ user: {} @@ -116,10 +144,10 @@ describe('AuthenticateHandler', function() { }); it('should succeed if token has valid `accessTokenExpiresAt` date', function() { - var model = { + const model = { getAccessToken: function() {} }; - var handler = new AuthenticateHandler({ model: model }); + const handler = new AuthenticateHandler({ model: model }); try { handler.validateAccessToken({ user: {}, @@ -134,11 +162,11 @@ describe('AuthenticateHandler', function() { describe('verifyScope()', function() { it('should call `model.getAccessToken()` if scope is defined', function() { - var model = { + const model = { getAccessToken: function() {}, verifyScope: sinon.stub().returns(true) }; - var handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: true, model: model, scope: 'bar' }); + const handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: true, model: model, scope: 'bar' }); return handler.verifyScope('foo') .then(function() { diff --git a/test/unit/handlers/authorize-handler_test.js b/test/unit/handlers/authorize-handler_test.js index fe9b6b1d7..376bc1e4a 100644 --- a/test/unit/handlers/authorize-handler_test.js +++ b/test/unit/handlers/authorize-handler_test.js @@ -4,12 +4,12 @@ * 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'); +const AuthorizeHandler = require('../../../lib/handlers/authorize-handler'); +const Request = require('../../../lib/request'); +const Response = require('../../../lib/response'); +const Promise = require('bluebird'); +const sinon = require('sinon'); +const should = require('chai').should(); /** * Test `AuthorizeHandler`. @@ -18,13 +18,13 @@ var should = require('should'); describe('AuthorizeHandler', function() { describe('generateAuthorizationCode()', function() { it('should call `model.generateAuthorizationCode()`', function() { - var model = { + const model = { generateAuthorizationCode: sinon.stub().returns({}), getAccessToken: function() {}, getClient: function() {}, saveAuthorizationCode: function() {} }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); return handler.generateAuthorizationCode() .then(function() { @@ -37,13 +37,13 @@ describe('AuthorizeHandler', function() { describe('getClient()', function() { it('should call `model.getClient()`', function() { - var model = { + const 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: {} }); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, method: {}, query: {} }); return handler.getClient(request) .then(function() { @@ -58,14 +58,14 @@ describe('AuthorizeHandler', function() { describe('getUser()', function() { it('should call `authenticateHandler.getUser()`', function() { - var authenticateHandler = { handle: sinon.stub().returns(Promise.resolve({})) }; - var model = { + const authenticateHandler = { handle: sinon.stub().returns(Promise.resolve({})) }; + const 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(); + const handler = new AuthorizeHandler({ authenticateHandler: authenticateHandler, authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: {}, headers: {}, method: {}, query: {} }); + const response = new Response(); return handler.getUser(request, response) .then(function() { @@ -80,12 +80,12 @@ describe('AuthorizeHandler', function() { describe('saveAuthorizationCode()', function() { it('should call `model.saveAuthorizationCode()`', function() { - var model = { + const model = { getAccessToken: function() {}, getClient: function() {}, saveAuthorizationCode: sinon.stub().returns({}) }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); return handler.saveAuthorizationCode('foo', 'bar', 'qux', 'biz', 'baz', 'boz') .then(function() { @@ -99,4 +99,80 @@ describe('AuthorizeHandler', function() { .catch(should.fail); }); }); + + describe('validateRedirectUri()', function() { + it('should call `model.validateRedirectUri()`', function() { + const client = { grants: ['authorization_code'], redirectUris: ['http://example.com/cb'] }; + const redirect_uri = 'http://example.com/cb/2'; + const model = { + getAccessToken: function() {}, + getClient: sinon.stub().returns(client), + saveAuthorizationCode: function() {}, + validateRedirectUri: sinon.stub().returns(true) + }; + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: { client_id: 12345, client_secret: 'secret', redirect_uri }, 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); + + model.validateRedirectUri.callCount.should.equal(1); + model.validateRedirectUri.firstCall.args.should.have.length(2); + model.validateRedirectUri.firstCall.args[0].should.equal(redirect_uri); + model.validateRedirectUri.firstCall.args[1].should.equal(client); + model.validateRedirectUri.firstCall.thisValue.should.equal(model); + }) + .catch(should.fail); + }); + + it('should be successful validation', function () { + const client = { grants: ['authorization_code'], redirectUris: ['http://example.com/cb'] }; + const redirect_uri = 'http://example.com/cb'; + const model = { + getAccessToken: function() {}, + getClient: sinon.stub().returns(client), + saveAuthorizationCode: function() {}, + validateRedirectUri: function (redirectUri, client) { + return client.redirectUris.includes(redirectUri); + } + }; + + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: { client_id: 12345, client_secret: 'secret', redirect_uri }, headers: {}, method: {}, query: {} }); + + return handler.getClient(request) + .then((client) => { + client.should.equal(client); + }); + }); + + it('should be unsuccessful validation', function () { + const client = { grants: ['authorization_code'], redirectUris: ['http://example.com/cb'] }; + const redirect_uri = 'http://example.com/callback'; + const model = { + getAccessToken: function() {}, + getClient: sinon.stub().returns(client), + saveAuthorizationCode: function() {}, + validateRedirectUri: function (redirectUri, client) { + return client.redirectUris.includes(redirectUri); + } + }; + + const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); + const request = new Request({ body: { client_id: 12345, client_secret: 'secret', redirect_uri }, headers: {}, method: {}, query: {} }); + + return handler.getClient(request) + .then(() => { + throw Error('should not resolve'); + }) + .catch((err) => { + err.name.should.equal('invalid_client'); + err.message.should.equal('Invalid client: `redirect_uri` does not match client value'); + }); + }); + }); }); diff --git a/test/unit/handlers/token-handler_test.js b/test/unit/handlers/token-handler_test.js index 2b37cd05a..8fe258b9d 100644 --- a/test/unit/handlers/token-handler_test.js +++ b/test/unit/handlers/token-handler_test.js @@ -4,10 +4,10 @@ * Module dependencies. */ -var Request = require('../../../lib/request'); -var TokenHandler = require('../../../lib/handlers/token-handler'); -var sinon = require('sinon'); -var should = require('should'); +const Request = require('../../../lib/request'); +const TokenHandler = require('../../../lib/handlers/token-handler'); +const sinon = require('sinon'); +const should = require('chai').should(); /** * Test `TokenHandler`. @@ -16,12 +16,12 @@ var should = require('should'); describe('TokenHandler', function() { describe('getClient()', function() { it('should call `model.getClient()`', function() { - var model = { + const 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: {} }); + const handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); + const request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, method: {}, query: {} }); return handler.getClient(request) .then(function() { diff --git a/test/unit/models/token-model_test.js b/test/unit/models/token-model_test.js index 3d899951c..7dcac615b 100644 --- a/test/unit/models/token-model_test.js +++ b/test/unit/models/token-model_test.js @@ -1,5 +1,5 @@ -var TokenModel = require('../../../lib/models/token-model'); - +const TokenModel = require('../../../lib/models/token-model'); +const should = require('chai').should(); /** * Test `Server`. */ @@ -7,18 +7,19 @@ var TokenModel = require('../../../lib/models/token-model'); describe('Model', function() { describe('constructor()', function() { it('should calculate `accessTokenLifetime` if `accessTokenExpiresAt` is set', function() { - var atExpiresAt = new Date(); + const atExpiresAt = new Date(); atExpiresAt.setHours(new Date().getHours() + 1); - var data = { - accessToken: 'foo', - client: 'bar', - user: 'tar', - accessTokenExpiresAt: atExpiresAt + const data = { + accessToken: 'foo', + client: 'bar', + user: 'tar', + accessTokenExpiresAt: atExpiresAt }; - var model = new TokenModel(data); - model.accessTokenLifetime.should.be.Number; + const model = new TokenModel(data); + should.exist(model.accessTokenLifetime); + model.accessTokenLifetime.should.a('number'); model.accessTokenLifetime.should.be.approximately(3600, 2); }); }); diff --git a/test/unit/request_test.js b/test/unit/request_test.js index 458cb8f9e..f292e2b4a 100644 --- a/test/unit/request_test.js +++ b/test/unit/request_test.js @@ -4,8 +4,8 @@ * Module dependencies. */ -var Request = require('../../lib/request'); -var should = require('should'); +const Request = require('../../lib/request'); +const should = require('chai').should(); /** * Test `Request`. @@ -28,9 +28,9 @@ function generateBaseRequest() { describe('Request', function() { it('should instantiate with a basic request', function() { - var originalRequest = generateBaseRequest(); + const originalRequest = generateBaseRequest(); - var request = new Request(originalRequest); + const request = new Request(originalRequest); request.headers.should.eql(originalRequest.headers); request.method.should.eql(originalRequest.method); request.query.should.eql(originalRequest.query); @@ -38,10 +38,10 @@ describe('Request', function() { }); it('should allow a request to be passed without a body', function() { - var originalRequest = generateBaseRequest(); + const originalRequest = generateBaseRequest(); delete originalRequest.body; - var request = new Request(originalRequest); + const request = new Request(originalRequest); request.headers.should.eql(originalRequest.headers); request.method.should.eql(originalRequest.method); request.query.should.eql(originalRequest.query); @@ -49,7 +49,7 @@ describe('Request', function() { }); it('should throw if headers are not passed to the constructor', function() { - var originalRequest = generateBaseRequest(); + const originalRequest = generateBaseRequest(); delete originalRequest.headers; (function() { @@ -58,7 +58,7 @@ describe('Request', function() { }); it('should throw if query string isn\'t passed to the constructor', function() { - var originalRequest = generateBaseRequest(); + const originalRequest = generateBaseRequest(); delete originalRequest.query; (function() { @@ -67,7 +67,7 @@ describe('Request', function() { }); it('should throw if method isn\'t passed to the constructor', function() { - var originalRequest = generateBaseRequest(); + const originalRequest = generateBaseRequest(); delete originalRequest.method; (function() { @@ -76,13 +76,13 @@ describe('Request', function() { }); it('should convert all header keys to lowercase', function() { - var originalRequest = generateBaseRequest(); + const originalRequest = generateBaseRequest(); originalRequest.headers = { Foo: 'bar', BAR: 'foo' }; - var request = new Request(originalRequest); + const request = new Request(originalRequest); request.headers.foo.should.eql('bar'); request.headers.bar.should.eql('foo'); should.not.exist(request.headers.Foo); @@ -90,7 +90,7 @@ describe('Request', function() { }); it('should include additional properties passed in the request', function() { - var originalRequest = generateBaseRequest(); + const originalRequest = generateBaseRequest(); originalRequest.custom = { newFoo: 'newBar' }; @@ -99,7 +99,7 @@ describe('Request', function() { newBar: 'newFoo' }; - var request = new Request(originalRequest); + const request = new Request(originalRequest); request.headers.should.eql(originalRequest.headers); request.method.should.eql(originalRequest.method); request.query.should.eql(originalRequest.query); @@ -109,7 +109,7 @@ describe('Request', function() { }); it('should include additional properties passed in the request', function() { - var originalRequest = generateBaseRequest(); + const originalRequest = generateBaseRequest(); originalRequest.custom = { newFoo: 'newBar' }; @@ -118,7 +118,7 @@ describe('Request', function() { newBar: 'newFoo' }; - var request = new Request(originalRequest); + const request = new Request(originalRequest); request.headers.should.eql(originalRequest.headers); request.method.should.eql(originalRequest.method); request.query.should.eql(originalRequest.query); @@ -128,41 +128,41 @@ describe('Request', function() { }); it('should allow getting of headers using `request.get`', function() { - var originalRequest = generateBaseRequest(); + const originalRequest = generateBaseRequest(); - var request = new Request(originalRequest); + const 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(); + const originalRequest = generateBaseRequest(); - var request = new Request(originalRequest); + const 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(); + const originalRequest = generateBaseRequest(); - var request = new Request(originalRequest); + const request = new Request(originalRequest); request.get('bar').should.eql(originalRequest.headers.bar); }); it('should validate the content-type', function() { - var originalRequest = generateBaseRequest(); + const 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); + 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', function() { - var originalRequest = generateBaseRequest(); + const 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); + const request = new Request(originalRequest); request.is('application/json').should.eql(false); }); }); diff --git a/test/unit/response_test.js b/test/unit/response_test.js index c435e32f7..8d4897c93 100644 --- a/test/unit/response_test.js +++ b/test/unit/response_test.js @@ -4,8 +4,8 @@ * Module dependencies. */ -var Response = require('../../lib/response'); -var should = require('should'); +const Response = require('../../lib/response'); +const should = require('chai').should(); /** * Test `Request`. @@ -24,42 +24,42 @@ function generateBaseResponse() { describe('Request', function() { it('should instantiate with a basic request', function() { - var originalResponse = generateBaseResponse(); + const originalResponse = generateBaseResponse(); - var response = new Response(originalResponse); + 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', function() { - var originalResponse = generateBaseResponse(); + const originalResponse = generateBaseResponse(); delete originalResponse.body; - var response = new Response(originalResponse); + 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', function() { - var originalResponse = generateBaseResponse(); + const originalResponse = generateBaseResponse(); delete originalResponse.headers; - var response = new Response(originalResponse); + 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', function() { - var originalResponse = generateBaseResponse(); + const originalResponse = generateBaseResponse(); originalResponse.headers = { Foo: 'bar', BAR: 'foo' }; - var response = new Response(originalResponse); + const response = new Response(originalResponse); response.headers.foo.should.eql('bar'); response.headers.bar.should.eql('foo'); should.not.exist(response.headers.Foo); @@ -67,7 +67,7 @@ describe('Request', function() { }); it('should include additional properties passed in the response', function() { - var originalResponse = generateBaseResponse(); + const originalResponse = generateBaseResponse(); originalResponse.custom = { newFoo: 'newBar' }; @@ -76,7 +76,7 @@ describe('Request', function() { newBar: 'newFoo' }; - var response = new Response(originalResponse); + const response = new Response(originalResponse); response.headers.should.eql(originalResponse.headers); response.body.should.eql(originalResponse.body); response.custom.should.eql(originalResponse.custom); @@ -84,37 +84,37 @@ describe('Request', function() { }); it('should allow getting of headers using `response.get`', function() { - var originalResponse = generateBaseResponse(); + const originalResponse = generateBaseResponse(); - var response = new Response(originalResponse); + const 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(); + const originalResponse = generateBaseResponse(); - var response = new Response(originalResponse); + const 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(); + const originalResponse = generateBaseResponse(); - var response = new Response(originalResponse); + const 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(); + it('should process redirect', function() { + const 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); - }); + const 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_test.js b/test/unit/server_test.js index e7c343f0c..3987df77a 100644 --- a/test/unit/server_test.js +++ b/test/unit/server_test.js @@ -4,12 +4,12 @@ * 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'); +const AuthenticateHandler = require('../../lib/handlers/authenticate-handler'); +const AuthorizeHandler = require('../../lib/handlers/authorize-handler'); +const Promise = require('bluebird'); +const Server = require('../../lib/server'); +const TokenHandler = require('../../lib/handlers/token-handler'); +const sinon = require('sinon'); /** * Test `Server`. @@ -18,10 +18,10 @@ var sinon = require('sinon'); describe('Server', function() { describe('authenticate()', function() { it('should call `handle`', function() { - var model = { + const model = { getAccessToken: function() {} }; - var server = new Server({ model: model }); + const server = new Server({ model: model }); sinon.stub(AuthenticateHandler.prototype, 'handle').returns(Promise.resolve()); @@ -33,11 +33,11 @@ describe('Server', function() { }); it('should map string passed as `options` to `options.scope`', function() { - var model = { + const model = { getAccessToken: function() {}, verifyScope: function() {} }; - var server = new Server({ model: model }); + const server = new Server({ model: model }); sinon.stub(AuthenticateHandler.prototype, 'handle').returns(Promise.resolve()); @@ -53,12 +53,12 @@ describe('Server', function() { describe('authorize()', function() { it('should call `handle`', function() { - var model = { + const model = { getAccessToken: function() {}, getClient: function() {}, saveAuthorizationCode: function() {} }; - var server = new Server({ model: model }); + const server = new Server({ model: model }); sinon.stub(AuthorizeHandler.prototype, 'handle').returns(Promise.resolve()); @@ -72,11 +72,11 @@ describe('Server', function() { describe('token()', function() { it('should call `handle`', function() { - var model = { + const model = { getClient: function() {}, saveToken: function() {} }; - var server = new Server({ model: model }); + const server = new Server({ model: model }); sinon.stub(TokenHandler.prototype, 'handle').returns(Promise.resolve());