diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..7a7bb1570 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +# Editor configuration, see http://editorconfig.org +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 000000000..665d45cf8 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,76 @@ +{ + "root": true, + "env": { + "browser": true, + "es6": true, + "node": true + }, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": "tsconfig.json", + "sourceType": "module" + }, + "plugins": ["@typescript-eslint", "import"], + "extends": [ + // Recommended defaults for ESLint: + "eslint:recommended", + // Turn off what's checked by TS compiler: + "plugin:@typescript-eslint/eslint-recommended", + // Turn on recommended TS-specific rules: + "plugin:@typescript-eslint/recommended", + // Turn on extra rules that require type-checking: + "plugin:@typescript-eslint/recommended-requiring-type-checking", + // Turn on rules for imports: + "plugin:import/typescript", + // Turn off rules conflicting with Prettier: + "prettier" + ], + "ignorePatterns": ["node_modules", "dist", "coverage"], + "rules": { + // This is already checked by Typescript's "noUnusedLocals" setting + "@typescript-eslint/no-unused-vars": "off", + + // No reason to disallow + "@typescript-eslint/no-inferrable-types": "off", + + // Optimize code for legibility, not for ease of parsing + "@typescript-eslint/no-use-before-define": "off", + + // Allow all interface names + "@typescript-eslint/interface-name-prefix": "off", + + // Require type annotations for return types, with some exceptions + "@typescript-eslint/explicit-function-return-type": [ + "warn", + { + "allowExpressions": true, + "allowTypedFunctionExpressions": true, + "allowHigherOrderFunctions": true + } + ], + + // Disallow default exports; only allow named exports + "import/no-default-export": "error", + + // Impose alphabetically ordered imports + "import/order": "error", + + // Standardize usage of array types (`T[]` or `Array`) + "@typescript-eslint/array-type": [ + "error", + { "default": "array-simple", "readonly": "generic" } + ], + + // Disallow variable names conflicting with deprecated globals + "no-restricted-globals": [ + "error", + "event", + "name", + "external", + "orientation" + ], + + // Disallow use of `console` + "no-console": "error" + } +} diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index 08b44961f..767c10e30 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,29 @@ -node_modules/ + +# IDE +/.idea +/.awcache +/.vscode/*.code-workspace +/.vscode/settings.json + +# misc +npm-debug.log + +# folders +#/dist +/node_modules docs/_build/ __pycache__/ + +# files +.DS_Store *.pyc +<<<<<<< HEAD +/aio + +# codecoverage +/.nyc_output +/coverage +======= lib-cov *.seed *.log @@ -34,3 +56,4 @@ tramp # Org-mode .org-id-locations *_archive +>>>>>>> e11930b8c35537b10f965c7390d7fe58622ba0f8 diff --git a/.jshintignore b/.jshintignore deleted file mode 100644 index 3c3629e64..000000000 --- a/.jshintignore +++ /dev/null @@ -1 +0,0 @@ -node_modules diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 441e9934f..000000000 --- a/.jshintrc +++ /dev/null @@ -1,26 +0,0 @@ -{ - "bitwise": true, - "curly": true, - "eqeqeq": true, - "esnext": true, - "expr": true, - "globalstrict": false, - "immed": true, - "indent": 2, - "jquery": true, - "latedef": false, - "mocha": true, - "newcap": true, - "noarg": true, - "node": true, - "noyield": true, - "predef": ["-Promise"], - "quotmark": "single", - "regexp": true, - "smarttabs": true, - "strict": false, - "trailing": false, - "undef": true, - "unused": true, - "white": false -} diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 65e3ba2ed..000000000 --- a/.npmignore +++ /dev/null @@ -1 +0,0 @@ -test/ diff --git a/.nycrc b/.nycrc new file mode 100644 index 000000000..7d93c0d94 --- /dev/null +++ b/.nycrc @@ -0,0 +1,10 @@ +{ + "extends": "@istanbuljs/nyc-config-typescript", + "all": true, + "reporter": [ + "lcovonly", + "html", + "text", + "text-summary" + ] +} \ No newline at end of file diff --git a/.patch b/.patch new file mode 100644 index 000000000..e69de29bb diff --git a/.prettierrc b/.prettierrc new file mode 100755 index 000000000..6de9cff5b --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "singleQuote": true, + "trailingComma": "all", + "endOfLine": "lf" +} diff --git a/.travis.yml b/.travis.yml old mode 100644 new mode 100755 index a5896daa2..f82c2b26b --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,11 @@ language: node_js node_js: +<<<<<<< HEAD + - 8 + - 10 + - 12 +======= - 4 - 6 - 8 @@ -8,5 +13,6 @@ node_js: - 12 - 13 - 14 +>>>>>>> e11930b8c35537b10f965c7390d7fe58622ba0f8 sudo: false diff --git a/.vscode/README.md b/.vscode/README.md new file mode 100644 index 000000000..9656796b7 --- /dev/null +++ b/.vscode/README.md @@ -0,0 +1,22 @@ +# VSCode Configuration + +This folder contains opt-in [Workspace Settings](https://code.visualstudio.com/docs/getstarted/settings) and [Extension Recommendations](https://code.visualstudio.com/docs/editor/extension-gallery#_workspace-recommended-extensions) that our team recommends using when working on this repository. + +## Usage + +To use the recommended settings follow the steps below: + +- copy `.vscode/recommended-settings.json` to `.vscode/settings.json` +- restart the editor + +If you already have your custom workspace settings you should instead manually merge the file content. + +This isn't an automatic process so you will need to repeat it when settings are updated. + +To see the recommended extensions select "Extensions: Show Recommended Extensions" in the [Command Palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette). + +## Editing `.vscode/recommended-settings.json` + +If you wish to add extra configuration items please keep in mind any settings you add here will be used by many users. + +Try to keep these settings to things that help facilitate the development process and avoid altering the user workflow whenever possible. diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000..7fac8f753 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,11 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + + // List of extensions which should be recommended for users of this workspace. + "recommendations": [ + "editorconfig.editorconfig", + "esbenp.prettier-vscode", + "ms-vscode.vscode-typescript-tslint-plugin" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..51fea6b85 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,23 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Mocha Tests", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", + "args": [ + "--require", + "ts-node/register", + "-u", + "tdd", + "--timeout", + "999999", + "--colors", + "--recursive", + "${workspaceFolder}/test/**/*.spec.ts" + ], + "internalConsoleOptions": "openOnSessionStart" + } + ] +} diff --git a/.vscode/recommended-settings.json b/.vscode/recommended-settings.json new file mode 100644 index 000000000..3fa8122cd --- /dev/null +++ b/.vscode/recommended-settings.json @@ -0,0 +1,39 @@ +{ + "files.exclude": { + "**/.git": true, + "**/.DS_Store": true, + "**/node_modules": true, + "dist": true + }, + "files.watcherExclude": { + "**/.git/**": true, + "**/node_modules/**": true, + "**/dist/**": true + }, + "search.exclude": { + "**/node_modules": true, + "**/dist": true + }, + "editor.formatOnSave": false, + "typescript.format.enable": false, + "editor.formatOnPaste": false, + "[typescript]": { + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": true, + "source.fixAll.tslint": true + } + }, + "[json]": { + "editor.formatOnSave": true + }, + "[jsonc]": { + "editor.formatOnSave": true + }, + "[javascript]": { + "editor.formatOnSave": true + }, + "prettier.singleQuote": true, + "prettier.trailingComma": "all", + "prettier.printWidth": 80 +} diff --git a/CHANGELOG.md b/CHANGELOG.md old mode 100644 new mode 100755 index 7e74b7b57..8924b040c --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,34 @@ ## Changelog +<<<<<<< HEAD +### 5.0.0 +* BREAKING: Remove support for node v6 +* BREAKING: Remove support for callbacks only support native Promises +* new: Rewrote in TypeScript +* new: Switch from Eslint to Tslint +* new: added .vscode folder for recommend extensions and recommend setting required for development +* new: added suitable TypeScript Interfaces for various Objects + +### 4.0.0 +* BREAKING: Set server_error Code to 500 +* BREAKING: Remove support for node v4 +* new: Added revoke-handler to revoke access token +* new: Added implicit grant flow +* new: Switch from jshint to eslin +* fix: authorization_code grant should not be required in implicit flowt + +### 3.1.0 +* new: Added package-lock.json +* new: Extend model object with request context +* new: .npmignore tests +* fix: validate requested scope on authorize request +* fix: issue correct expiry dates for tokens +======= ### 3.1.0 * new: .npmignore tests * fix: validate requested scope on authorize request * fix: always issue correct expiry dates for tokens +>>>>>>> e11930b8c35537b10f965c7390d7fe58622ba0f8 * fix: set numArgs for promisify of generateAuthorizationCode * fix: Changed 'hasOwnProperty' call in Response * docs: Ensure accessTokenExpiresAt is required @@ -12,6 +37,12 @@ * readme: Update Slack badge and link * readme: Fix link to RFC6750 standard +<<<<<<< HEAD +### 3.0.1 +* Updated dependencies + +### 3.0.0 +======= ### 3.0.2 (24/05/2020) * Update all dependencies 🎉 @@ -23,6 +54,7 @@ Tag never released on npm ### 3.0.0 (04/08/2017) +>>>>>>> e11930b8c35537b10f965c7390d7fe58622ba0f8 * Complete re-write, with Promises and callback support * Dropped support for node v0.8, v0.10, v0.12 * Supports Node v4, v6, v7, and v8. Will continue support for node current and active LTS versions diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 diff --git a/README.md b/README.md index dc2122371..fef419894 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ +<<<<<<< HEAD +======= +>>>>>>> e11930b8c35537b10f965c7390d7fe58622ba0f8 # oauth2-server [![npm Version][npm-image]][npm-url] @@ -22,7 +25,7 @@ The *oauth2-server* module is framework-agnostic but there are several officiall ## Features -- Supports `authorization_code`, `client_credentials`, `refresh_token` and `password` grant, as well as *extension grants*, with scopes. +- Supports `authorization_code`, `client_credentials`, `refresh_token`, `implicit` and `password` grant, as well as *extension grants*, with scopes. - Can be used with *promises*, *Node-style callbacks*, *ES6 generators* and *async*/*await* (using [Babel](https://babeljs.io)). - Fully [RFC 6749](https://tools.ietf.org/html/rfc6749.html) and [RFC 6750](https://tools.ietf.org/html/rfc6750.html) compliant. - Implicitly supports any form of storage, e.g. *PostgreSQL*, *MySQL*, *MongoDB*, *Redis*, etc. diff --git a/TODO b/TODO new file mode 100644 index 000000000..723eeaabc --- /dev/null +++ b/TODO @@ -0,0 +1,8 @@ + +Todo: + ✔ Add a todo + ✔ A Basic Rewrite of library in TypeScript + ✔ A Basic Rewrite of tests in TypeScript + ☐ Add examples for nestjs, expressjs, koa and others + ☐ Add Migration guide + ☐ Review all Docs diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 000000000..3cea1819c --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1,10 @@ +export * from './lib/errors'; +export * from './lib/grant-types'; +export * from './lib/handlers'; +export * from './lib/interfaces'; +export { Request } from './lib/request'; +export { Response } from './lib/response'; +export * from './lib/response-types'; +export { OAuth2Server } from './lib/server'; +export * from './lib/token-types'; +export * from './lib/validator/is'; diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 000000000..887f4e46b --- /dev/null +++ b/dist/index.js @@ -0,0 +1,17 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const tslib_1 = require("tslib"); +tslib_1.__exportStar(require("./lib/errors"), exports); +tslib_1.__exportStar(require("./lib/grant-types"), exports); +tslib_1.__exportStar(require("./lib/handlers"), exports); +tslib_1.__exportStar(require("./lib/interfaces"), exports); +var request_1 = require("./lib/request"); +Object.defineProperty(exports, "Request", { enumerable: true, get: function () { return request_1.Request; } }); +var response_1 = require("./lib/response"); +Object.defineProperty(exports, "Response", { enumerable: true, get: function () { return response_1.Response; } }); +tslib_1.__exportStar(require("./lib/response-types"), exports); +var server_1 = require("./lib/server"); +Object.defineProperty(exports, "OAuth2Server", { enumerable: true, get: function () { return server_1.OAuth2Server; } }); +tslib_1.__exportStar(require("./lib/token-types"), exports); +tslib_1.__exportStar(require("./lib/validator/is"), exports); +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/index.js.map b/dist/index.js.map new file mode 100644 index 000000000..8acbee74b --- /dev/null +++ b/dist/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":";;;AAAA,uDAA6B;AAC7B,4DAAkC;AAClC,yDAA+B;AAC/B,2DAAiC;AACjC,yCAAwC;AAA/B,kGAAA,OAAO,OAAA;AAChB,2CAA0C;AAAjC,oGAAA,QAAQ,OAAA;AACjB,+DAAqC;AACrC,uCAA4C;AAAnC,sGAAA,YAAY,OAAA;AACrB,4DAAkC;AAClC,6DAAmC"} \ No newline at end of file diff --git a/dist/lib/constants/common.d.ts b/dist/lib/constants/common.d.ts new file mode 100644 index 000000000..f9fb3fe9a --- /dev/null +++ b/dist/lib/constants/common.d.ts @@ -0,0 +1,11 @@ +export declare const MILLISECONDS_PER_SECOND = 1000; +export declare const SECONDS_PER_MINUTE = 60; +export declare const MINUTES_PER_HOUR = 60; +export declare const HOURS_PER_DAY = 24; +export declare const DAYS_PER_WEEK = 7; +export declare const MONTHS_PER_YEAR = 12; +export declare const SECOND = 1000; +export declare const MINUTE: number; +export declare const HOUR: number; +export declare const DAY: number; +export declare const WEEK: number; diff --git a/dist/lib/constants/common.js b/dist/lib/constants/common.js new file mode 100644 index 000000000..2cce18e16 --- /dev/null +++ b/dist/lib/constants/common.js @@ -0,0 +1,15 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.WEEK = exports.DAY = exports.HOUR = exports.MINUTE = exports.SECOND = exports.MONTHS_PER_YEAR = exports.DAYS_PER_WEEK = exports.HOURS_PER_DAY = exports.MINUTES_PER_HOUR = exports.SECONDS_PER_MINUTE = exports.MILLISECONDS_PER_SECOND = void 0; +exports.MILLISECONDS_PER_SECOND = 1000; +exports.SECONDS_PER_MINUTE = 60; +exports.MINUTES_PER_HOUR = 60; +exports.HOURS_PER_DAY = 24; +exports.DAYS_PER_WEEK = 7; +exports.MONTHS_PER_YEAR = 12; +exports.SECOND = exports.MILLISECONDS_PER_SECOND; +exports.MINUTE = exports.SECONDS_PER_MINUTE * exports.SECOND; +exports.HOUR = exports.MINUTES_PER_HOUR * exports.MINUTE; +exports.DAY = exports.HOURS_PER_DAY * exports.HOUR; +exports.WEEK = exports.DAYS_PER_WEEK * exports.DAY; +//# sourceMappingURL=common.js.map \ No newline at end of file diff --git a/dist/lib/constants/common.js.map b/dist/lib/constants/common.js.map new file mode 100644 index 000000000..496a1cf61 --- /dev/null +++ b/dist/lib/constants/common.js.map @@ -0,0 +1 @@ +{"version":3,"file":"common.js","sourceRoot":"","sources":["../../../lib/constants/common.ts"],"names":[],"mappings":";;;AAAa,QAAA,uBAAuB,GAAG,IAAK,CAAC;AAChC,QAAA,kBAAkB,GAAG,EAAE,CAAC;AACxB,QAAA,gBAAgB,GAAG,EAAE,CAAC;AACtB,QAAA,aAAa,GAAG,EAAE,CAAC;AACnB,QAAA,aAAa,GAAG,CAAC,CAAC;AAClB,QAAA,eAAe,GAAG,EAAE,CAAC;AAErB,QAAA,MAAM,GAAG,+BAAuB,CAAC;AACjC,QAAA,MAAM,GAAG,0BAAkB,GAAG,cAAM,CAAC;AACrC,QAAA,IAAI,GAAG,wBAAgB,GAAG,cAAM,CAAC;AACjC,QAAA,GAAG,GAAG,qBAAa,GAAG,YAAI,CAAC;AAC3B,QAAA,IAAI,GAAG,qBAAa,GAAG,WAAG,CAAC"} \ No newline at end of file diff --git a/dist/lib/constants/index.d.ts b/dist/lib/constants/index.d.ts new file mode 100644 index 000000000..d0b932366 --- /dev/null +++ b/dist/lib/constants/index.d.ts @@ -0,0 +1 @@ +export * from './common'; diff --git a/dist/lib/constants/index.js b/dist/lib/constants/index.js new file mode 100644 index 000000000..60cc60c44 --- /dev/null +++ b/dist/lib/constants/index.js @@ -0,0 +1,5 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const tslib_1 = require("tslib"); +tslib_1.__exportStar(require("./common"), exports); +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/lib/constants/index.js.map b/dist/lib/constants/index.js.map new file mode 100644 index 000000000..40af03c4a --- /dev/null +++ b/dist/lib/constants/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../lib/constants/index.ts"],"names":[],"mappings":";;;AAAA,mDAAyB"} \ No newline at end of file diff --git a/dist/lib/errors/access-denied-error.d.ts b/dist/lib/errors/access-denied-error.d.ts new file mode 100644 index 000000000..2f3d69dad --- /dev/null +++ b/dist/lib/errors/access-denied-error.d.ts @@ -0,0 +1,4 @@ +import { OAuthError } from './oauth-error'; +export declare class AccessDeniedError extends OAuthError { + constructor(message?: string | Error, properties?: any); +} diff --git a/dist/lib/errors/access-denied-error.js b/dist/lib/errors/access-denied-error.js new file mode 100644 index 000000000..10bc63d2f --- /dev/null +++ b/dist/lib/errors/access-denied-error.js @@ -0,0 +1,11 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AccessDeniedError = void 0; +const oauth_error_1 = require("./oauth-error"); +class AccessDeniedError extends oauth_error_1.OAuthError { + constructor(message = '', properties) { + super(message, Object.assign({ code: 400, name: 'access_denied' }, properties)); + } +} +exports.AccessDeniedError = AccessDeniedError; +//# sourceMappingURL=access-denied-error.js.map \ No newline at end of file diff --git a/dist/lib/errors/access-denied-error.js.map b/dist/lib/errors/access-denied-error.js.map new file mode 100644 index 000000000..1aea9043e --- /dev/null +++ b/dist/lib/errors/access-denied-error.js.map @@ -0,0 +1 @@ +{"version":3,"file":"access-denied-error.js","sourceRoot":"","sources":["../../../lib/errors/access-denied-error.ts"],"names":[],"mappings":";;;AAAA,+CAA2C;AAU3C,MAAa,iBAAkB,SAAQ,wBAAU;IAC/C,YAAY,UAA0B,EAAE,EAAE,UAAgB;QACxD,KAAK,CAAC,OAAO,kBAAI,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,eAAe,IAAK,UAAU,EAAG,CAAC;IACtE,CAAC;CACF;AAJD,8CAIC"} \ No newline at end of file diff --git a/dist/lib/errors/index.d.ts b/dist/lib/errors/index.d.ts new file mode 100644 index 000000000..24141cb6f --- /dev/null +++ b/dist/lib/errors/index.d.ts @@ -0,0 +1,14 @@ +export { AccessDeniedError } from './access-denied-error'; +export { InsufficientScopeError } from './insufficient-scope-error'; +export { InvalidArgumentError } from './invalid-argument-error'; +export { InvalidClientError } from './invalid-client-error'; +export { InvalidGrantError } from './invalid-grant-error'; +export { InvalidRequestError } from './invalid-request-error'; +export { InvalidScopeError } from './invalid-scope-error'; +export { InvalidTokenError } from './invalid-token-error'; +export { OAuthError } from './oauth-error'; +export { ServerError } from './server-error'; +export { UnauthorizedClientError } from './unauthorized-client-error'; +export { UnauthorizedRequestError } from './unauthorized-request-error'; +export { UnsupportedGrantTypeError } from './unsupported-grant-type-error'; +export { UnsupportedResponseTypeError } from './unsupported-response-type-error'; diff --git a/dist/lib/errors/index.js b/dist/lib/errors/index.js new file mode 100644 index 000000000..5c9e90719 --- /dev/null +++ b/dist/lib/errors/index.js @@ -0,0 +1,31 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var access_denied_error_1 = require("./access-denied-error"); +Object.defineProperty(exports, "AccessDeniedError", { enumerable: true, get: function () { return access_denied_error_1.AccessDeniedError; } }); +var insufficient_scope_error_1 = require("./insufficient-scope-error"); +Object.defineProperty(exports, "InsufficientScopeError", { enumerable: true, get: function () { return insufficient_scope_error_1.InsufficientScopeError; } }); +var invalid_argument_error_1 = require("./invalid-argument-error"); +Object.defineProperty(exports, "InvalidArgumentError", { enumerable: true, get: function () { return invalid_argument_error_1.InvalidArgumentError; } }); +var invalid_client_error_1 = require("./invalid-client-error"); +Object.defineProperty(exports, "InvalidClientError", { enumerable: true, get: function () { return invalid_client_error_1.InvalidClientError; } }); +var invalid_grant_error_1 = require("./invalid-grant-error"); +Object.defineProperty(exports, "InvalidGrantError", { enumerable: true, get: function () { return invalid_grant_error_1.InvalidGrantError; } }); +var invalid_request_error_1 = require("./invalid-request-error"); +Object.defineProperty(exports, "InvalidRequestError", { enumerable: true, get: function () { return invalid_request_error_1.InvalidRequestError; } }); +var invalid_scope_error_1 = require("./invalid-scope-error"); +Object.defineProperty(exports, "InvalidScopeError", { enumerable: true, get: function () { return invalid_scope_error_1.InvalidScopeError; } }); +var invalid_token_error_1 = require("./invalid-token-error"); +Object.defineProperty(exports, "InvalidTokenError", { enumerable: true, get: function () { return invalid_token_error_1.InvalidTokenError; } }); +var oauth_error_1 = require("./oauth-error"); +Object.defineProperty(exports, "OAuthError", { enumerable: true, get: function () { return oauth_error_1.OAuthError; } }); +var server_error_1 = require("./server-error"); +Object.defineProperty(exports, "ServerError", { enumerable: true, get: function () { return server_error_1.ServerError; } }); +var unauthorized_client_error_1 = require("./unauthorized-client-error"); +Object.defineProperty(exports, "UnauthorizedClientError", { enumerable: true, get: function () { return unauthorized_client_error_1.UnauthorizedClientError; } }); +var unauthorized_request_error_1 = require("./unauthorized-request-error"); +Object.defineProperty(exports, "UnauthorizedRequestError", { enumerable: true, get: function () { return unauthorized_request_error_1.UnauthorizedRequestError; } }); +var unsupported_grant_type_error_1 = require("./unsupported-grant-type-error"); +Object.defineProperty(exports, "UnsupportedGrantTypeError", { enumerable: true, get: function () { return unsupported_grant_type_error_1.UnsupportedGrantTypeError; } }); +var unsupported_response_type_error_1 = require("./unsupported-response-type-error"); +Object.defineProperty(exports, "UnsupportedResponseTypeError", { enumerable: true, get: function () { return unsupported_response_type_error_1.UnsupportedResponseTypeError; } }); +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/lib/errors/index.js.map b/dist/lib/errors/index.js.map new file mode 100644 index 000000000..070512111 --- /dev/null +++ b/dist/lib/errors/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../lib/errors/index.ts"],"names":[],"mappings":";;AAAA,6DAA0D;AAAjD,wHAAA,iBAAiB,OAAA;AAC1B,uEAAoE;AAA3D,kIAAA,sBAAsB,OAAA;AAC/B,mEAAgE;AAAvD,8HAAA,oBAAoB,OAAA;AAC7B,+DAA4D;AAAnD,0HAAA,kBAAkB,OAAA;AAC3B,6DAA0D;AAAjD,wHAAA,iBAAiB,OAAA;AAC1B,iEAA8D;AAArD,4HAAA,mBAAmB,OAAA;AAC5B,6DAA0D;AAAjD,wHAAA,iBAAiB,OAAA;AAC1B,6DAA0D;AAAjD,wHAAA,iBAAiB,OAAA;AAC1B,6CAA2C;AAAlC,yGAAA,UAAU,OAAA;AACnB,+CAA6C;AAApC,2GAAA,WAAW,OAAA;AACpB,yEAAsE;AAA7D,oIAAA,uBAAuB,OAAA;AAChC,2EAAwE;AAA/D,sIAAA,wBAAwB,OAAA;AACjC,+EAA2E;AAAlE,yIAAA,yBAAyB,OAAA;AAClC,qFAAiF;AAAxE,+IAAA,4BAA4B,OAAA"} \ No newline at end of file diff --git a/dist/lib/errors/insufficient-scope-error.d.ts b/dist/lib/errors/insufficient-scope-error.d.ts new file mode 100644 index 000000000..46068dcf5 --- /dev/null +++ b/dist/lib/errors/insufficient-scope-error.d.ts @@ -0,0 +1,4 @@ +import { OAuthError } from './oauth-error'; +export declare class InsufficientScopeError extends OAuthError { + constructor(message?: string | Error, properties?: any); +} diff --git a/dist/lib/errors/insufficient-scope-error.js b/dist/lib/errors/insufficient-scope-error.js new file mode 100644 index 000000000..9bf94a6ae --- /dev/null +++ b/dist/lib/errors/insufficient-scope-error.js @@ -0,0 +1,11 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.InsufficientScopeError = void 0; +const oauth_error_1 = require("./oauth-error"); +class InsufficientScopeError extends oauth_error_1.OAuthError { + constructor(message = '', properties) { + super(message, Object.assign({ code: 403, name: 'insufficient_scope' }, properties)); + } +} +exports.InsufficientScopeError = InsufficientScopeError; +//# sourceMappingURL=insufficient-scope-error.js.map \ No newline at end of file diff --git a/dist/lib/errors/insufficient-scope-error.js.map b/dist/lib/errors/insufficient-scope-error.js.map new file mode 100644 index 000000000..98094258f --- /dev/null +++ b/dist/lib/errors/insufficient-scope-error.js.map @@ -0,0 +1 @@ +{"version":3,"file":"insufficient-scope-error.js","sourceRoot":"","sources":["../../../lib/errors/insufficient-scope-error.ts"],"names":[],"mappings":";;;AAAA,+CAA2C;AAU3C,MAAa,sBAAuB,SAAQ,wBAAU;IACpD,YAAY,UAA0B,EAAE,EAAE,UAAgB;QACxD,KAAK,CAAC,OAAO,kBAAI,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,oBAAoB,IAAK,UAAU,EAAG,CAAC;IAC3E,CAAC;CACF;AAJD,wDAIC"} \ No newline at end of file diff --git a/dist/lib/errors/invalid-argument-error.d.ts b/dist/lib/errors/invalid-argument-error.d.ts new file mode 100644 index 000000000..6b06c4232 --- /dev/null +++ b/dist/lib/errors/invalid-argument-error.d.ts @@ -0,0 +1,4 @@ +import { OAuthError } from './oauth-error'; +export declare class InvalidArgumentError extends OAuthError { + constructor(message?: string | Error, properties?: any); +} diff --git a/dist/lib/errors/invalid-argument-error.js b/dist/lib/errors/invalid-argument-error.js new file mode 100644 index 000000000..3d374a4ec --- /dev/null +++ b/dist/lib/errors/invalid-argument-error.js @@ -0,0 +1,11 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.InvalidArgumentError = void 0; +const oauth_error_1 = require("./oauth-error"); +class InvalidArgumentError extends oauth_error_1.OAuthError { + constructor(message = '', properties) { + super(message, Object.assign({ code: 500, name: 'invalid_argument' }, properties)); + } +} +exports.InvalidArgumentError = InvalidArgumentError; +//# sourceMappingURL=invalid-argument-error.js.map \ No newline at end of file diff --git a/dist/lib/errors/invalid-argument-error.js.map b/dist/lib/errors/invalid-argument-error.js.map new file mode 100644 index 000000000..11c1e3f75 --- /dev/null +++ b/dist/lib/errors/invalid-argument-error.js.map @@ -0,0 +1 @@ +{"version":3,"file":"invalid-argument-error.js","sourceRoot":"","sources":["../../../lib/errors/invalid-argument-error.ts"],"names":[],"mappings":";;;AAAA,+CAA2C;AAO3C,MAAa,oBAAqB,SAAQ,wBAAU;IAClD,YAAY,UAA0B,EAAE,EAAE,UAAgB;QACxD,KAAK,CAAC,OAAO,kBAAI,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,kBAAkB,IAAK,UAAU,EAAG,CAAC;IACzE,CAAC;CACF;AAJD,oDAIC"} \ No newline at end of file diff --git a/dist/lib/errors/invalid-client-error.d.ts b/dist/lib/errors/invalid-client-error.d.ts new file mode 100644 index 000000000..d8c25f445 --- /dev/null +++ b/dist/lib/errors/invalid-client-error.d.ts @@ -0,0 +1,4 @@ +import { OAuthError } from './oauth-error'; +export declare class InvalidClientError extends OAuthError { + constructor(message?: string | Error, properties?: any); +} diff --git a/dist/lib/errors/invalid-client-error.js b/dist/lib/errors/invalid-client-error.js new file mode 100644 index 000000000..219c9414f --- /dev/null +++ b/dist/lib/errors/invalid-client-error.js @@ -0,0 +1,11 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.InvalidClientError = void 0; +const oauth_error_1 = require("./oauth-error"); +class InvalidClientError extends oauth_error_1.OAuthError { + constructor(message = '', properties) { + super(message, Object.assign({ code: 400, name: 'invalid_client' }, properties)); + } +} +exports.InvalidClientError = InvalidClientError; +//# sourceMappingURL=invalid-client-error.js.map \ No newline at end of file diff --git a/dist/lib/errors/invalid-client-error.js.map b/dist/lib/errors/invalid-client-error.js.map new file mode 100644 index 000000000..42af0447d --- /dev/null +++ b/dist/lib/errors/invalid-client-error.js.map @@ -0,0 +1 @@ +{"version":3,"file":"invalid-client-error.js","sourceRoot":"","sources":["../../../lib/errors/invalid-client-error.ts"],"names":[],"mappings":";;;AAAA,+CAA2C;AAW3C,MAAa,kBAAmB,SAAQ,wBAAU;IAChD,YAAY,UAA0B,EAAE,EAAE,UAAgB;QACxD,KAAK,CAAC,OAAO,kBAAI,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,gBAAgB,IAAK,UAAU,EAAG,CAAC;IACvE,CAAC;CACF;AAJD,gDAIC"} \ No newline at end of file diff --git a/dist/lib/errors/invalid-grant-error.d.ts b/dist/lib/errors/invalid-grant-error.d.ts new file mode 100644 index 000000000..aaa2976f9 --- /dev/null +++ b/dist/lib/errors/invalid-grant-error.d.ts @@ -0,0 +1,4 @@ +import { OAuthError } from './oauth-error'; +export declare class InvalidGrantError extends OAuthError { + constructor(message?: string | Error, properties?: any); +} diff --git a/dist/lib/errors/invalid-grant-error.js b/dist/lib/errors/invalid-grant-error.js new file mode 100644 index 000000000..406003388 --- /dev/null +++ b/dist/lib/errors/invalid-grant-error.js @@ -0,0 +1,11 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.InvalidGrantError = void 0; +const oauth_error_1 = require("./oauth-error"); +class InvalidGrantError extends oauth_error_1.OAuthError { + constructor(message = '', properties) { + super(message, Object.assign({ code: 400, name: 'invalid_grant' }, properties)); + } +} +exports.InvalidGrantError = InvalidGrantError; +//# sourceMappingURL=invalid-grant-error.js.map \ No newline at end of file diff --git a/dist/lib/errors/invalid-grant-error.js.map b/dist/lib/errors/invalid-grant-error.js.map new file mode 100644 index 000000000..87ee09276 --- /dev/null +++ b/dist/lib/errors/invalid-grant-error.js.map @@ -0,0 +1 @@ +{"version":3,"file":"invalid-grant-error.js","sourceRoot":"","sources":["../../../lib/errors/invalid-grant-error.ts"],"names":[],"mappings":";;;AAAA,+CAA2C;AAY3C,MAAa,iBAAkB,SAAQ,wBAAU;IAC/C,YAAY,UAA0B,EAAE,EAAE,UAAgB;QACxD,KAAK,CAAC,OAAO,kBAAI,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,eAAe,IAAK,UAAU,EAAG,CAAC;IACtE,CAAC;CACF;AAJD,8CAIC"} \ No newline at end of file diff --git a/dist/lib/errors/invalid-request-error.d.ts b/dist/lib/errors/invalid-request-error.d.ts new file mode 100644 index 000000000..f8abf0036 --- /dev/null +++ b/dist/lib/errors/invalid-request-error.d.ts @@ -0,0 +1,4 @@ +import { OAuthError } from './oauth-error'; +export declare class InvalidRequestError extends OAuthError { + constructor(message?: string | Error, properties?: any); +} diff --git a/dist/lib/errors/invalid-request-error.js b/dist/lib/errors/invalid-request-error.js new file mode 100644 index 000000000..b58bcf7f2 --- /dev/null +++ b/dist/lib/errors/invalid-request-error.js @@ -0,0 +1,11 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.InvalidRequestError = void 0; +const oauth_error_1 = require("./oauth-error"); +class InvalidRequestError extends oauth_error_1.OAuthError { + constructor(message = '', properties) { + super(message, Object.assign({ code: 400, name: 'invalid_request' }, properties)); + } +} +exports.InvalidRequestError = InvalidRequestError; +//# sourceMappingURL=invalid-request-error.js.map \ No newline at end of file diff --git a/dist/lib/errors/invalid-request-error.js.map b/dist/lib/errors/invalid-request-error.js.map new file mode 100644 index 000000000..1db52f863 --- /dev/null +++ b/dist/lib/errors/invalid-request-error.js.map @@ -0,0 +1 @@ +{"version":3,"file":"invalid-request-error.js","sourceRoot":"","sources":["../../../lib/errors/invalid-request-error.ts"],"names":[],"mappings":";;;AAAA,+CAA2C;AAW3C,MAAa,mBAAoB,SAAQ,wBAAU;IACjD,YAAY,UAA0B,EAAE,EAAE,UAAgB;QACxD,KAAK,CAAC,OAAO,kBAAI,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,iBAAiB,IAAK,UAAU,EAAG,CAAC;IACxE,CAAC;CACF;AAJD,kDAIC"} \ No newline at end of file diff --git a/dist/lib/errors/invalid-scope-error.d.ts b/dist/lib/errors/invalid-scope-error.d.ts new file mode 100644 index 000000000..5fe9b812b --- /dev/null +++ b/dist/lib/errors/invalid-scope-error.d.ts @@ -0,0 +1,4 @@ +import { OAuthError } from './oauth-error'; +export declare class InvalidScopeError extends OAuthError { + constructor(message?: string | Error, properties?: any); +} diff --git a/dist/lib/errors/invalid-scope-error.js b/dist/lib/errors/invalid-scope-error.js new file mode 100644 index 000000000..8acc60700 --- /dev/null +++ b/dist/lib/errors/invalid-scope-error.js @@ -0,0 +1,11 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.InvalidScopeError = void 0; +const oauth_error_1 = require("./oauth-error"); +class InvalidScopeError extends oauth_error_1.OAuthError { + constructor(message = '', properties) { + super(message, Object.assign({ code: 400, name: 'invalid_scope' }, properties)); + } +} +exports.InvalidScopeError = InvalidScopeError; +//# sourceMappingURL=invalid-scope-error.js.map \ No newline at end of file diff --git a/dist/lib/errors/invalid-scope-error.js.map b/dist/lib/errors/invalid-scope-error.js.map new file mode 100644 index 000000000..0e955e4bc --- /dev/null +++ b/dist/lib/errors/invalid-scope-error.js.map @@ -0,0 +1 @@ +{"version":3,"file":"invalid-scope-error.js","sourceRoot":"","sources":["../../../lib/errors/invalid-scope-error.ts"],"names":[],"mappings":";;;AAAA,+CAA2C;AAU3C,MAAa,iBAAkB,SAAQ,wBAAU;IAC/C,YAAY,UAA0B,EAAE,EAAE,UAAgB;QACxD,KAAK,CAAC,OAAO,kBAAI,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,eAAe,IAAK,UAAU,EAAG,CAAC;IACtE,CAAC;CACF;AAJD,8CAIC"} \ No newline at end of file diff --git a/dist/lib/errors/invalid-token-error.d.ts b/dist/lib/errors/invalid-token-error.d.ts new file mode 100644 index 000000000..e7e179c46 --- /dev/null +++ b/dist/lib/errors/invalid-token-error.d.ts @@ -0,0 +1,4 @@ +import { OAuthError } from './oauth-error'; +export declare class InvalidTokenError extends OAuthError { + constructor(message?: string | Error, properties?: any); +} diff --git a/dist/lib/errors/invalid-token-error.js b/dist/lib/errors/invalid-token-error.js new file mode 100644 index 000000000..0bbe35057 --- /dev/null +++ b/dist/lib/errors/invalid-token-error.js @@ -0,0 +1,11 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.InvalidTokenError = void 0; +const oauth_error_1 = require("./oauth-error"); +class InvalidTokenError extends oauth_error_1.OAuthError { + constructor(message = '', properties) { + super(message, Object.assign({ code: 401, name: 'invalid_token' }, properties)); + } +} +exports.InvalidTokenError = InvalidTokenError; +//# sourceMappingURL=invalid-token-error.js.map \ No newline at end of file diff --git a/dist/lib/errors/invalid-token-error.js.map b/dist/lib/errors/invalid-token-error.js.map new file mode 100644 index 000000000..d193da260 --- /dev/null +++ b/dist/lib/errors/invalid-token-error.js.map @@ -0,0 +1 @@ +{"version":3,"file":"invalid-token-error.js","sourceRoot":"","sources":["../../../lib/errors/invalid-token-error.ts"],"names":[],"mappings":";;;AAAA,+CAA2C;AAU3C,MAAa,iBAAkB,SAAQ,wBAAU;IAC/C,YAAY,UAA0B,EAAE,EAAE,UAAgB;QACxD,KAAK,CAAC,OAAO,kBAAI,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,eAAe,IAAK,UAAU,EAAG,CAAC;IACtE,CAAC;CACF;AAJD,8CAIC"} \ No newline at end of file diff --git a/dist/lib/errors/oauth-error.d.ts b/dist/lib/errors/oauth-error.d.ts new file mode 100644 index 000000000..f9615f0ed --- /dev/null +++ b/dist/lib/errors/oauth-error.d.ts @@ -0,0 +1,6 @@ +export declare class OAuthError extends Error { + code: any; + status: any; + statusCode: any; + constructor(messageOrError: string | Error, properties?: any); +} diff --git a/dist/lib/errors/oauth-error.js b/dist/lib/errors/oauth-error.js new file mode 100644 index 000000000..85889281a --- /dev/null +++ b/dist/lib/errors/oauth-error.js @@ -0,0 +1,29 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.OAuthError = void 0; +const statuses = require("statuses"); +class OAuthError extends Error { + constructor(messageOrError, properties = {}) { + super(); + let message = messageOrError instanceof Error ? messageOrError.message : messageOrError; + const error = messageOrError instanceof Error ? messageOrError : undefined; + let props = {}; + props = properties; + props.code = props.code || 500; + if (error) { + props.inner = error; + } + if (!message) { + message = statuses[props.code]; + } + this.code = this.status = this.statusCode = props.code; + this.message = message; + const ignoreAttr = ['code', 'message']; + Object.keys(props) + .filter(key => !ignoreAttr.includes(key)) + .forEach(key => (this[key] = props[key])); + Error.captureStackTrace(this, OAuthError); + } +} +exports.OAuthError = OAuthError; +//# sourceMappingURL=oauth-error.js.map \ No newline at end of file diff --git a/dist/lib/errors/oauth-error.js.map b/dist/lib/errors/oauth-error.js.map new file mode 100644 index 000000000..dc7335bcf --- /dev/null +++ b/dist/lib/errors/oauth-error.js.map @@ -0,0 +1 @@ +{"version":3,"file":"oauth-error.js","sourceRoot":"","sources":["../../../lib/errors/oauth-error.ts"],"names":[],"mappings":";;;AAAA,qCAAqC;AAErC,MAAa,UAAW,SAAQ,KAAK;IAInC,YAAY,cAA8B,EAAE,aAAkB,EAAE;QAC9D,KAAK,EAAE,CAAC;QACR,IAAI,OAAO,GACT,cAAc,YAAY,KAAK,CAAC,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC;QAC5E,MAAM,KAAK,GAAG,cAAc,YAAY,KAAK,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS,CAAC;QAC3E,IAAI,KAAK,GAAQ,EAAE,CAAC;QACpB,KAAK,GAAG,UAAU,CAAC;QACnB,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,IAAI,GAAG,CAAC;QAE/B,IAAI,KAAK,EAAE;YACT,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC;SACrB;QACD,IAAI,CAAC,OAAO,EAAE;YACZ,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;SAChC;QACD,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC;QACvD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QAEvB,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QACvC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC;aACf,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;aACxC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAE5C,KAAK,CAAC,iBAAiB,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IAC5C,CAAC;CACF;AA7BD,gCA6BC"} \ No newline at end of file diff --git a/dist/lib/errors/server-error.d.ts b/dist/lib/errors/server-error.d.ts new file mode 100644 index 000000000..225578f07 --- /dev/null +++ b/dist/lib/errors/server-error.d.ts @@ -0,0 +1,4 @@ +import { OAuthError } from './oauth-error'; +export declare class ServerError extends OAuthError { + constructor(message?: string | Error, properties?: any); +} diff --git a/dist/lib/errors/server-error.js b/dist/lib/errors/server-error.js new file mode 100644 index 000000000..94a503e5d --- /dev/null +++ b/dist/lib/errors/server-error.js @@ -0,0 +1,11 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ServerError = void 0; +const oauth_error_1 = require("./oauth-error"); +class ServerError extends oauth_error_1.OAuthError { + constructor(message = '', properties) { + super(message, Object.assign({ code: 500, name: 'server_error' }, properties)); + } +} +exports.ServerError = ServerError; +//# sourceMappingURL=server-error.js.map \ No newline at end of file diff --git a/dist/lib/errors/server-error.js.map b/dist/lib/errors/server-error.js.map new file mode 100644 index 000000000..713307c11 --- /dev/null +++ b/dist/lib/errors/server-error.js.map @@ -0,0 +1 @@ +{"version":3,"file":"server-error.js","sourceRoot":"","sources":["../../../lib/errors/server-error.ts"],"names":[],"mappings":";;;AAAA,+CAA2C;AAU3C,MAAa,WAAY,SAAQ,wBAAU;IACzC,YAAY,UAA0B,EAAE,EAAE,UAAgB;QACxD,KAAK,CAAC,OAAO,kBAAI,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,cAAc,IAAK,UAAU,EAAG,CAAC;IACrE,CAAC;CACF;AAJD,kCAIC"} \ No newline at end of file diff --git a/dist/lib/errors/unauthorized-client-error.d.ts b/dist/lib/errors/unauthorized-client-error.d.ts new file mode 100644 index 000000000..f615916ce --- /dev/null +++ b/dist/lib/errors/unauthorized-client-error.d.ts @@ -0,0 +1,4 @@ +import { OAuthError } from './oauth-error'; +export declare class UnauthorizedClientError extends OAuthError { + constructor(message?: string | Error, properties?: any); +} diff --git a/dist/lib/errors/unauthorized-client-error.js b/dist/lib/errors/unauthorized-client-error.js new file mode 100644 index 000000000..26701d798 --- /dev/null +++ b/dist/lib/errors/unauthorized-client-error.js @@ -0,0 +1,11 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.UnauthorizedClientError = void 0; +const oauth_error_1 = require("./oauth-error"); +class UnauthorizedClientError extends oauth_error_1.OAuthError { + constructor(message = '', properties) { + super(message, Object.assign({ code: 400, name: 'unauthorized_client' }, properties)); + } +} +exports.UnauthorizedClientError = UnauthorizedClientError; +//# sourceMappingURL=unauthorized-client-error.js.map \ No newline at end of file diff --git a/dist/lib/errors/unauthorized-client-error.js.map b/dist/lib/errors/unauthorized-client-error.js.map new file mode 100644 index 000000000..7be2aa3e5 --- /dev/null +++ b/dist/lib/errors/unauthorized-client-error.js.map @@ -0,0 +1 @@ +{"version":3,"file":"unauthorized-client-error.js","sourceRoot":"","sources":["../../../lib/errors/unauthorized-client-error.ts"],"names":[],"mappings":";;;AAAA,+CAA2C;AAU3C,MAAa,uBAAwB,SAAQ,wBAAU;IACrD,YAAY,UAA0B,EAAE,EAAE,UAAgB;QACxD,KAAK,CAAC,OAAO,kBAAI,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,qBAAqB,IAAK,UAAU,EAAG,CAAC;IAC5E,CAAC;CACF;AAJD,0DAIC"} \ No newline at end of file diff --git a/dist/lib/errors/unauthorized-request-error.d.ts b/dist/lib/errors/unauthorized-request-error.d.ts new file mode 100644 index 000000000..6d0f17d62 --- /dev/null +++ b/dist/lib/errors/unauthorized-request-error.d.ts @@ -0,0 +1,4 @@ +import { OAuthError } from './oauth-error'; +export declare class UnauthorizedRequestError extends OAuthError { + constructor(message?: string | Error, properties?: any); +} diff --git a/dist/lib/errors/unauthorized-request-error.js b/dist/lib/errors/unauthorized-request-error.js new file mode 100644 index 000000000..62e90505f --- /dev/null +++ b/dist/lib/errors/unauthorized-request-error.js @@ -0,0 +1,11 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.UnauthorizedRequestError = void 0; +const oauth_error_1 = require("./oauth-error"); +class UnauthorizedRequestError extends oauth_error_1.OAuthError { + constructor(message = '', properties) { + super(message, Object.assign({ code: 401, name: 'unauthorized_request' }, properties)); + } +} +exports.UnauthorizedRequestError = UnauthorizedRequestError; +//# sourceMappingURL=unauthorized-request-error.js.map \ No newline at end of file diff --git a/dist/lib/errors/unauthorized-request-error.js.map b/dist/lib/errors/unauthorized-request-error.js.map new file mode 100644 index 000000000..9c22063fe --- /dev/null +++ b/dist/lib/errors/unauthorized-request-error.js.map @@ -0,0 +1 @@ +{"version":3,"file":"unauthorized-request-error.js","sourceRoot":"","sources":["../../../lib/errors/unauthorized-request-error.ts"],"names":[],"mappings":";;;AAAA,+CAA2C;AAa3C,MAAa,wBAAyB,SAAQ,wBAAU;IACtD,YAAY,UAA0B,EAAE,EAAE,UAAgB;QACxD,KAAK,CAAC,OAAO,kBAAI,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,sBAAsB,IAAK,UAAU,EAAG,CAAC;IAC7E,CAAC;CACF;AAJD,4DAIC"} \ No newline at end of file diff --git a/dist/lib/errors/unsupported-grant-type-error.d.ts b/dist/lib/errors/unsupported-grant-type-error.d.ts new file mode 100644 index 000000000..aea43d3a4 --- /dev/null +++ b/dist/lib/errors/unsupported-grant-type-error.d.ts @@ -0,0 +1,4 @@ +import { OAuthError } from './oauth-error'; +export declare class UnsupportedGrantTypeError extends OAuthError { + constructor(message?: string | Error, properties?: any); +} diff --git a/dist/lib/errors/unsupported-grant-type-error.js b/dist/lib/errors/unsupported-grant-type-error.js new file mode 100644 index 000000000..1690708f0 --- /dev/null +++ b/dist/lib/errors/unsupported-grant-type-error.js @@ -0,0 +1,11 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.UnsupportedGrantTypeError = void 0; +const oauth_error_1 = require("./oauth-error"); +class UnsupportedGrantTypeError extends oauth_error_1.OAuthError { + constructor(message = '', properties) { + super(message, Object.assign({ code: 400, name: 'unsupported_grant_type' }, properties)); + } +} +exports.UnsupportedGrantTypeError = UnsupportedGrantTypeError; +//# sourceMappingURL=unsupported-grant-type-error.js.map \ No newline at end of file diff --git a/dist/lib/errors/unsupported-grant-type-error.js.map b/dist/lib/errors/unsupported-grant-type-error.js.map new file mode 100644 index 000000000..5e5d8d079 --- /dev/null +++ b/dist/lib/errors/unsupported-grant-type-error.js.map @@ -0,0 +1 @@ +{"version":3,"file":"unsupported-grant-type-error.js","sourceRoot":"","sources":["../../../lib/errors/unsupported-grant-type-error.ts"],"names":[],"mappings":";;;AAAA,+CAA2C;AAU3C,MAAa,yBAA0B,SAAQ,wBAAU;IACvD,YAAY,UAA0B,EAAE,EAAE,UAAgB;QACxD,KAAK,CAAC,OAAO,kBACX,IAAI,EAAE,GAAG,EACT,IAAI,EAAE,wBAAwB,IAC3B,UAAU,EACb,CAAC;IACL,CAAC;CACF;AARD,8DAQC"} \ No newline at end of file diff --git a/dist/lib/errors/unsupported-response-type-error.d.ts b/dist/lib/errors/unsupported-response-type-error.d.ts new file mode 100644 index 000000000..f66cef1b4 --- /dev/null +++ b/dist/lib/errors/unsupported-response-type-error.d.ts @@ -0,0 +1,4 @@ +import { OAuthError } from './oauth-error'; +export declare class UnsupportedResponseTypeError extends OAuthError { + constructor(message?: string | Error, properties?: any); +} diff --git a/dist/lib/errors/unsupported-response-type-error.js b/dist/lib/errors/unsupported-response-type-error.js new file mode 100644 index 000000000..c6c89411c --- /dev/null +++ b/dist/lib/errors/unsupported-response-type-error.js @@ -0,0 +1,11 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.UnsupportedResponseTypeError = void 0; +const oauth_error_1 = require("./oauth-error"); +class UnsupportedResponseTypeError extends oauth_error_1.OAuthError { + constructor(message = '', properties) { + super(message, Object.assign({ code: 400, name: 'unsupported_response_type' }, properties)); + } +} +exports.UnsupportedResponseTypeError = UnsupportedResponseTypeError; +//# sourceMappingURL=unsupported-response-type-error.js.map \ No newline at end of file diff --git a/dist/lib/errors/unsupported-response-type-error.js.map b/dist/lib/errors/unsupported-response-type-error.js.map new file mode 100644 index 000000000..36da4c458 --- /dev/null +++ b/dist/lib/errors/unsupported-response-type-error.js.map @@ -0,0 +1 @@ +{"version":3,"file":"unsupported-response-type-error.js","sourceRoot":"","sources":["../../../lib/errors/unsupported-response-type-error.ts"],"names":[],"mappings":";;;AAAA,+CAA2C;AAW3C,MAAa,4BAA6B,SAAQ,wBAAU;IAC1D,YAAY,UAA0B,EAAE,EAAE,UAAgB;QACxD,KAAK,CAAC,OAAO,kBACX,IAAI,EAAE,GAAG,EACT,IAAI,EAAE,2BAA2B,IAC9B,UAAU,EACb,CAAC;IACL,CAAC;CACF;AARD,oEAQC"} \ No newline at end of file diff --git a/dist/lib/grant-types/abstract-grant-type.d.ts b/dist/lib/grant-types/abstract-grant-type.d.ts new file mode 100644 index 000000000..b5ab42e1f --- /dev/null +++ b/dist/lib/grant-types/abstract-grant-type.d.ts @@ -0,0 +1,15 @@ +import { Client, Model, User } from '../interfaces'; +import { Request } from '../request'; +export declare class AbstractGrantType { + accessTokenLifetime: number; + model: Model; + refreshTokenLifetime: number; + alwaysIssueNewRefreshToken: boolean; + constructor(options?: any); + generateAccessToken(client?: Client, user?: User, scope?: string): Promise; + generateRefreshToken(client?: Client, user?: User, scope?: string): Promise; + getAccessTokenExpiresAt(): Date; + getRefreshTokenExpiresAt(): Date; + getScope(request: Request): any; + validateScope(user: User, client: Client, scope: string): Promise; +} diff --git a/dist/lib/grant-types/abstract-grant-type.js b/dist/lib/grant-types/abstract-grant-type.js new file mode 100644 index 000000000..7010d1d98 --- /dev/null +++ b/dist/lib/grant-types/abstract-grant-type.js @@ -0,0 +1,59 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AbstractGrantType = void 0; +const constants_1 = require("../constants"); +const errors_1 = require("../errors"); +const tokenUtil = require("../utils/token-util"); +const is = require("../validator/is"); +class AbstractGrantType { + constructor(options = {}) { + if (!options.accessTokenLifetime) { + throw new errors_1.InvalidArgumentError('Missing parameter: `accessTokenLifetime`'); + } + if (!options.model) { + throw new errors_1.InvalidArgumentError('Missing parameter: `model`'); + } + this.accessTokenLifetime = options.accessTokenLifetime; + this.model = options.model; + this.refreshTokenLifetime = options.refreshTokenLifetime; + this.alwaysIssueNewRefreshToken = options.alwaysIssueNewRefreshToken; + } + async generateAccessToken(client, user, scope) { + if (this.model.generateAccessToken) { + const token = await this.model.generateAccessToken(client, user, scope); + return token ? token : tokenUtil.GenerateRandomToken(); + } + return tokenUtil.GenerateRandomToken(); + } + async generateRefreshToken(client, user, scope) { + if (this.model.generateRefreshToken) { + const token = await this.model.generateRefreshToken(client, user, scope); + return token ? token : tokenUtil.GenerateRandomToken(); + } + return tokenUtil.GenerateRandomToken(); + } + getAccessTokenExpiresAt() { + return new Date(Date.now() + this.accessTokenLifetime * constants_1.MILLISECONDS_PER_SECOND); + } + getRefreshTokenExpiresAt() { + return new Date(Date.now() + this.refreshTokenLifetime * constants_1.MILLISECONDS_PER_SECOND); + } + getScope(request) { + if (!is.nqschar(request.body.scope)) { + throw new errors_1.InvalidArgumentError('Invalid parameter: `scope`'); + } + return request.body.scope; + } + async validateScope(user, client, scope) { + if (this.model.validateScope) { + const validatedScope = await this.model.validateScope(user, client, scope); + if (!validatedScope) { + throw new errors_1.InvalidScopeError('Invalid scope: Requested scope is invalid'); + } + return validatedScope; + } + return scope; + } +} +exports.AbstractGrantType = AbstractGrantType; +//# sourceMappingURL=abstract-grant-type.js.map \ No newline at end of file diff --git a/dist/lib/grant-types/abstract-grant-type.js.map b/dist/lib/grant-types/abstract-grant-type.js.map new file mode 100644 index 000000000..b26295d94 --- /dev/null +++ b/dist/lib/grant-types/abstract-grant-type.js.map @@ -0,0 +1 @@ +{"version":3,"file":"abstract-grant-type.js","sourceRoot":"","sources":["../../../lib/grant-types/abstract-grant-type.ts"],"names":[],"mappings":";;;AAAA,4CAAuD;AACvD,sCAAoE;AAGpE,iDAAiD;AACjD,sCAAsC;AAEtC,MAAa,iBAAiB;IAM5B,YAAY,UAAe,EAAE;QAC3B,IAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE;YAChC,MAAM,IAAI,6BAAoB,CAC5B,0CAA0C,CAC3C,CAAC;SACH;QAED,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE;YAClB,MAAM,IAAI,6BAAoB,CAAC,4BAA4B,CAAC,CAAC;SAC9D;QAED,IAAI,CAAC,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC;QACvD,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;QAC3B,IAAI,CAAC,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC;QACzD,IAAI,CAAC,0BAA0B,GAAG,OAAO,CAAC,0BAA0B,CAAC;IACvE,CAAC;IAMD,KAAK,CAAC,mBAAmB,CAAC,MAAe,EAAE,IAAW,EAAE,KAAc;QACpE,IAAI,IAAI,CAAC,KAAK,CAAC,mBAAmB,EAAE;YAClC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;YAExE,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,mBAAmB,EAAE,CAAC;SACxD;QAED,OAAO,SAAS,CAAC,mBAAmB,EAAE,CAAC;IACzC,CAAC;IAMD,KAAK,CAAC,oBAAoB,CAAC,MAAe,EAAE,IAAW,EAAE,KAAc;QACrE,IAAI,IAAI,CAAC,KAAK,CAAC,oBAAoB,EAAE;YACnC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;YAEzE,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,mBAAmB,EAAE,CAAC;SACxD;QAED,OAAO,SAAS,CAAC,mBAAmB,EAAE,CAAC;IACzC,CAAC;IAMD,uBAAuB;QACrB,OAAO,IAAI,IAAI,CACb,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,mBAAmB,GAAG,mCAAuB,CAChE,CAAC;IACJ,CAAC;IAMD,wBAAwB;QACtB,OAAO,IAAI,IAAI,CACb,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,oBAAoB,GAAG,mCAAuB,CACjE,CAAC;IACJ,CAAC;IAMD,QAAQ,CAAC,OAAgB;QACvB,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE;YACnC,MAAM,IAAI,6BAAoB,CAAC,4BAA4B,CAAC,CAAC;SAC9D;QAED,OAAO,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;IAC5B,CAAC;IAKD,KAAK,CAAC,aAAa,CAAC,IAAU,EAAE,MAAc,EAAE,KAAa;QAC3D,IAAI,IAAI,CAAC,KAAK,CAAC,aAAa,EAAE;YAC5B,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,aAAa,CACnD,IAAI,EACJ,MAAM,EACN,KAAK,CACN,CAAC;YACF,IAAI,CAAC,cAAc,EAAE;gBACnB,MAAM,IAAI,0BAAiB,CACzB,2CAA2C,CAC5C,CAAC;aACH;YAED,OAAO,cAAc,CAAC;SACvB;QAED,OAAO,KAAK,CAAC;IACf,CAAC;CACF;AAxGD,8CAwGC"} \ No newline at end of file diff --git a/dist/lib/grant-types/authorization-code-grant-type.d.ts b/dist/lib/grant-types/authorization-code-grant-type.d.ts new file mode 100644 index 000000000..74203b281 --- /dev/null +++ b/dist/lib/grant-types/authorization-code-grant-type.d.ts @@ -0,0 +1,11 @@ +import { AbstractGrantType } from '.'; +import { AuthorizationCode, Client, Token, User } from '../interfaces'; +import { Request } from '../request'; +export declare class AuthorizationCodeGrantType extends AbstractGrantType { + constructor(options?: any); + handle(request: Request, client: Client): Promise; + getAuthorizationCode(request: Request, client: Client): Promise; + validateRedirectUri(request: Request, code: AuthorizationCode): void; + revokeAuthorizationCode(code: AuthorizationCode): Promise; + saveToken(user: User, client: Client, authorizationCode: string, scope: string): Promise; +} diff --git a/dist/lib/grant-types/authorization-code-grant-type.js b/dist/lib/grant-types/authorization-code-grant-type.js new file mode 100644 index 000000000..bae094586 --- /dev/null +++ b/dist/lib/grant-types/authorization-code-grant-type.js @@ -0,0 +1,103 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AuthorizationCodeGrantType = void 0; +const _1 = require("."); +const errors_1 = require("../errors"); +const is = require("../validator/is"); +class AuthorizationCodeGrantType extends _1.AbstractGrantType { + constructor(options = {}) { + super(options); + if (!options.model) { + throw new errors_1.InvalidArgumentError('Missing parameter: `model`'); + } + if (!options.model.getAuthorizationCode) { + throw new errors_1.InvalidArgumentError('Invalid argument: model does not implement `getAuthorizationCode()`'); + } + if (!options.model.revokeAuthorizationCode) { + throw new errors_1.InvalidArgumentError('Invalid argument: model does not implement `revokeAuthorizationCode()`'); + } + if (!options.model.saveToken) { + throw new errors_1.InvalidArgumentError('Invalid argument: model does not implement `saveToken()`'); + } + } + async handle(request, client) { + if (!request) { + throw new errors_1.InvalidArgumentError('Missing parameter: `request`'); + } + if (!client) { + throw new errors_1.InvalidArgumentError('Missing parameter: `client`'); + } + const code = await this.getAuthorizationCode(request, client); + this.validateRedirectUri(request, code); + await this.revokeAuthorizationCode(code); + return this.saveToken(code.user, client, code.authorizationCode, code.scope); + } + async getAuthorizationCode(request, client) { + if (!request.body.code) { + throw new errors_1.InvalidRequestError('Missing parameter: `code`'); + } + if (!is.vschar(request.body.code)) { + throw new errors_1.InvalidRequestError('Invalid parameter: `code`'); + } + const code = await this.model.getAuthorizationCode(request.body.code); + if (!code) { + throw new errors_1.InvalidGrantError('Invalid grant: authorization code is invalid'); + } + if (!code.client) { + throw new errors_1.ServerError('Server error: `getAuthorizationCode()` did not return a `client` object'); + } + if (!code.user) { + throw new errors_1.ServerError('Server error: `getAuthorizationCode()` did not return a `user` object'); + } + if (code.client.id !== client.id) { + throw new errors_1.InvalidGrantError('Invalid grant: authorization code is invalid'); + } + if (!(code.expiresAt instanceof Date)) { + throw new errors_1.ServerError('Server error: `expiresAt` must be a Date instance'); + } + if (code.expiresAt.getTime() < Date.now()) { + throw new errors_1.InvalidGrantError('Invalid grant: authorization code has expired'); + } + if (code.redirectUri && !is.uri(code.redirectUri)) { + throw new errors_1.InvalidGrantError('Invalid grant: `redirect_uri` is not a valid URI'); + } + return code; + } + validateRedirectUri(request, code) { + if (!code.redirectUri) { + return; + } + const redirectUri = request.body.redirect_uri || request.query.redirect_uri; + if (!is.uri(redirectUri)) { + throw new errors_1.InvalidRequestError('Invalid request: `redirect_uri` is not a valid URI'); + } + if (redirectUri !== code.redirectUri) { + throw new errors_1.InvalidRequestError('Invalid request: `redirect_uri` is invalid'); + } + } + async revokeAuthorizationCode(code) { + const status = await this.model.revokeAuthorizationCode(code); + if (!status) { + throw new errors_1.InvalidGrantError('Invalid grant: authorization code is invalid'); + } + return code; + } + async saveToken(user, client, authorizationCode, scope) { + const accessScope = await this.validateScope(user, client, scope); + const accessToken = await this.generateAccessToken(client, user, scope); + const refreshToken = await this.generateRefreshToken(client, user, scope); + const accessTokenExpiresAt = this.getAccessTokenExpiresAt(); + const refreshTokenExpiresAt = this.getRefreshTokenExpiresAt(); + const token = { + accessToken, + authorizationCode, + accessTokenExpiresAt, + refreshToken, + refreshTokenExpiresAt, + scope: accessScope, + }; + return this.model.saveToken(token, client, user); + } +} +exports.AuthorizationCodeGrantType = AuthorizationCodeGrantType; +//# sourceMappingURL=authorization-code-grant-type.js.map \ No newline at end of file diff --git a/dist/lib/grant-types/authorization-code-grant-type.js.map b/dist/lib/grant-types/authorization-code-grant-type.js.map new file mode 100644 index 000000000..0953e83ab --- /dev/null +++ b/dist/lib/grant-types/authorization-code-grant-type.js.map @@ -0,0 +1 @@ +{"version":3,"file":"authorization-code-grant-type.js","sourceRoot":"","sources":["../../../lib/grant-types/authorization-code-grant-type.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/lib/grant-types/client-credentials-grant-type.d.ts b/dist/lib/grant-types/client-credentials-grant-type.d.ts new file mode 100644 index 000000000..7704adf45 --- /dev/null +++ b/dist/lib/grant-types/client-credentials-grant-type.d.ts @@ -0,0 +1,9 @@ +import { AbstractGrantType } from '.'; +import { Client, Token, User } from '../interfaces'; +import { Request } from '../request'; +export declare class ClientCredentialsGrantType extends AbstractGrantType { + constructor(options?: any); + handle(request: Request, client: Client): Promise; + getUserFromClient(client: Client): Promise; + saveToken(user: User, client: Client, scope: string): Promise; +} diff --git a/dist/lib/grant-types/client-credentials-grant-type.js b/dist/lib/grant-types/client-credentials-grant-type.js new file mode 100644 index 000000000..1430c9058 --- /dev/null +++ b/dist/lib/grant-types/client-credentials-grant-type.js @@ -0,0 +1,50 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ClientCredentialsGrantType = void 0; +const _1 = require("."); +const errors_1 = require("../errors"); +class ClientCredentialsGrantType extends _1.AbstractGrantType { + constructor(options = {}) { + super(options); + if (!options.model) { + throw new errors_1.InvalidArgumentError('Missing parameter: `model`'); + } + if (!options.model.getUserFromClient) { + throw new errors_1.InvalidArgumentError('Invalid argument: model does not implement `getUserFromClient()`'); + } + if (!options.model.saveToken) { + throw new errors_1.InvalidArgumentError('Invalid argument: model does not implement `saveToken()`'); + } + } + async handle(request, client) { + if (!request) { + throw new errors_1.InvalidArgumentError('Missing parameter: `request`'); + } + if (!client) { + throw new errors_1.InvalidArgumentError('Missing parameter: `client`'); + } + const scope = this.getScope(request); + const user = await this.getUserFromClient(client); + return this.saveToken(user, client, scope); + } + async getUserFromClient(client) { + const user = await this.model.getUserFromClient(client); + if (!user) { + throw new errors_1.InvalidGrantError('Invalid grant: user credentials are invalid'); + } + return user; + } + async saveToken(user, client, scope) { + const accessScope = await this.validateScope(user, client, scope); + const accessToken = await this.generateAccessToken(client, user, scope); + const accessTokenExpiresAt = this.getAccessTokenExpiresAt(); + const token = { + accessToken, + accessTokenExpiresAt, + scope: accessScope, + }; + return this.model.saveToken(token, client, user); + } +} +exports.ClientCredentialsGrantType = ClientCredentialsGrantType; +//# sourceMappingURL=client-credentials-grant-type.js.map \ No newline at end of file diff --git a/dist/lib/grant-types/client-credentials-grant-type.js.map b/dist/lib/grant-types/client-credentials-grant-type.js.map new file mode 100644 index 000000000..8dbe5b6db --- /dev/null +++ b/dist/lib/grant-types/client-credentials-grant-type.js.map @@ -0,0 +1 @@ +{"version":3,"file":"client-credentials-grant-type.js","sourceRoot":"","sources":["../../../lib/grant-types/client-credentials-grant-type.ts"],"names":[],"mappings":";;;AAAA,wBAAsC;AACtC,sCAAoE;AAIpE,MAAa,0BAA2B,SAAQ,oBAAiB;IAC/D,YAAY,UAAe,EAAE;QAC3B,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE;YAClB,MAAM,IAAI,6BAAoB,CAAC,4BAA4B,CAAC,CAAC;SAC9D;QAED,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,iBAAiB,EAAE;YACpC,MAAM,IAAI,6BAAoB,CAC5B,kEAAkE,CACnE,CAAC;SACH;QAED,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE;YAC5B,MAAM,IAAI,6BAAoB,CAC5B,0DAA0D,CAC3D,CAAC;SACH;IACH,CAAC;IAQD,KAAK,CAAC,MAAM,CAAC,OAAgB,EAAE,MAAc;QAC3C,IAAI,CAAC,OAAO,EAAE;YACZ,MAAM,IAAI,6BAAoB,CAAC,8BAA8B,CAAC,CAAC;SAChE;QAED,IAAI,CAAC,MAAM,EAAE;YACX,MAAM,IAAI,6BAAoB,CAAC,6BAA6B,CAAC,CAAC;SAC/D;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACrC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC;QAElD,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;IAC7C,CAAC;IAMD,KAAK,CAAC,iBAAiB,CAAC,MAAc;QACpC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC;QACxD,IAAI,CAAC,IAAI,EAAE;YACT,MAAM,IAAI,0BAAiB,CACzB,6CAA6C,CAC9C,CAAC;SACH;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAMD,KAAK,CAAC,SAAS,CAAC,IAAU,EAAE,MAAc,EAAE,KAAa;QACvD,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;QAClE,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;QACxE,MAAM,oBAAoB,GAAG,IAAI,CAAC,uBAAuB,EAAE,CAAC;QAE5D,MAAM,KAAK,GAAG;YACZ,WAAW;YACX,oBAAoB;YACpB,KAAK,EAAE,WAAW;SACV,CAAC;QAEX,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;IACnD,CAAC;CACF;AAzED,gEAyEC"} \ No newline at end of file diff --git a/dist/lib/grant-types/implicit-grant-type.d.ts b/dist/lib/grant-types/implicit-grant-type.d.ts new file mode 100644 index 000000000..0be845a10 --- /dev/null +++ b/dist/lib/grant-types/implicit-grant-type.d.ts @@ -0,0 +1,10 @@ +import { AbstractGrantType } from '.'; +import { Client, Token, User } from '../interfaces'; +import { Request } from '../request'; +export declare class ImplicitGrantType extends AbstractGrantType { + scope: string; + user: User; + constructor(options?: any); + handle(request: Request, client: Client): Promise; + saveToken(user: User, client: Client, scope: string): Promise; +} diff --git a/dist/lib/grant-types/implicit-grant-type.js b/dist/lib/grant-types/implicit-grant-type.js new file mode 100644 index 000000000..87bbb0904 --- /dev/null +++ b/dist/lib/grant-types/implicit-grant-type.js @@ -0,0 +1,43 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ImplicitGrantType = void 0; +const _1 = require("."); +const errors_1 = require("../errors"); +class ImplicitGrantType extends _1.AbstractGrantType { + constructor(options = {}) { + super(options); + if (!options.model) { + throw new errors_1.InvalidArgumentError('Missing parameter: `model`'); + } + if (!options.model.saveToken) { + throw new errors_1.InvalidArgumentError('Invalid argument: model does not implement `saveToken()`'); + } + if (!options.user) { + throw new errors_1.InvalidArgumentError('Missing parameter: `user`'); + } + this.scope = options.scope; + this.user = options.user; + } + async handle(request, client) { + if (!request) { + throw new errors_1.InvalidArgumentError('Missing parameter: `request`'); + } + if (!client) { + throw new errors_1.InvalidArgumentError('Missing parameter: `client`'); + } + return this.saveToken(this.user, client, this.scope); + } + async saveToken(user, client, scope) { + const validatedScope = await this.validateScope(user, client, scope); + const accessToken = await this.generateAccessToken(client, user, scope); + const accessTokenExpiresAt = this.getAccessTokenExpiresAt(); + const token = { + accessToken, + accessTokenExpiresAt, + scope: validatedScope, + }; + return this.model.saveToken(token, client, user); + } +} +exports.ImplicitGrantType = ImplicitGrantType; +//# sourceMappingURL=implicit-grant-type.js.map \ No newline at end of file diff --git a/dist/lib/grant-types/implicit-grant-type.js.map b/dist/lib/grant-types/implicit-grant-type.js.map new file mode 100644 index 000000000..f620bd652 --- /dev/null +++ b/dist/lib/grant-types/implicit-grant-type.js.map @@ -0,0 +1 @@ +{"version":3,"file":"implicit-grant-type.js","sourceRoot":"","sources":["../../../lib/grant-types/implicit-grant-type.ts"],"names":[],"mappings":";;;AAAA,wBAAsC;AACtC,sCAAiD;AAIjD,MAAa,iBAAkB,SAAQ,oBAAiB;IAGtD,YAAY,UAAe,EAAE;QAC3B,KAAK,CAAC,OAAO,CAAC,CAAC;QAEf,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE;YAClB,MAAM,IAAI,6BAAoB,CAAC,4BAA4B,CAAC,CAAC;SAC9D;QAED,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE;YAC5B,MAAM,IAAI,6BAAoB,CAC5B,0DAA0D,CAC3D,CAAC;SACH;QAED,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE;YACjB,MAAM,IAAI,6BAAoB,CAAC,2BAA2B,CAAC,CAAC;SAC7D;QAED,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;QAC3B,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAC3B,CAAC;IAMD,KAAK,CAAC,MAAM,CAAC,OAAgB,EAAE,MAAc;QAC3C,IAAI,CAAC,OAAO,EAAE;YACZ,MAAM,IAAI,6BAAoB,CAAC,8BAA8B,CAAC,CAAC;SAChE;QAED,IAAI,CAAC,MAAM,EAAE;YACX,MAAM,IAAI,6BAAoB,CAAC,6BAA6B,CAAC,CAAC;SAC/D;QAED,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IACvD,CAAC;IAMD,KAAK,CAAC,SAAS,CAAC,IAAU,EAAE,MAAc,EAAE,KAAa;QACvD,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;QACrE,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;QACxE,MAAM,oBAAoB,GAAG,IAAI,CAAC,uBAAuB,EAAE,CAAC;QAE5D,MAAM,KAAK,GAAG;YACZ,WAAW;YACX,oBAAoB;YACpB,KAAK,EAAE,cAAc;SACb,CAAC;QAEX,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;IACnD,CAAC;CACF;AAzDD,8CAyDC"} \ No newline at end of file diff --git a/dist/lib/grant-types/index.d.ts b/dist/lib/grant-types/index.d.ts new file mode 100644 index 000000000..3d0ad0d4b --- /dev/null +++ b/dist/lib/grant-types/index.d.ts @@ -0,0 +1,6 @@ +export { AbstractGrantType } from './abstract-grant-type'; +export { AuthorizationCodeGrantType } from './authorization-code-grant-type'; +export { ClientCredentialsGrantType } from './client-credentials-grant-type'; +export { ImplicitGrantType } from './implicit-grant-type'; +export { PasswordGrantType } from './password-grant-type'; +export { RefreshTokenGrantType } from './refresh-token-grant-type'; diff --git a/dist/lib/grant-types/index.js b/dist/lib/grant-types/index.js new file mode 100644 index 000000000..4b0e69b2c --- /dev/null +++ b/dist/lib/grant-types/index.js @@ -0,0 +1,15 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var abstract_grant_type_1 = require("./abstract-grant-type"); +Object.defineProperty(exports, "AbstractGrantType", { enumerable: true, get: function () { return abstract_grant_type_1.AbstractGrantType; } }); +var authorization_code_grant_type_1 = require("./authorization-code-grant-type"); +Object.defineProperty(exports, "AuthorizationCodeGrantType", { enumerable: true, get: function () { return authorization_code_grant_type_1.AuthorizationCodeGrantType; } }); +var client_credentials_grant_type_1 = require("./client-credentials-grant-type"); +Object.defineProperty(exports, "ClientCredentialsGrantType", { enumerable: true, get: function () { return client_credentials_grant_type_1.ClientCredentialsGrantType; } }); +var implicit_grant_type_1 = require("./implicit-grant-type"); +Object.defineProperty(exports, "ImplicitGrantType", { enumerable: true, get: function () { return implicit_grant_type_1.ImplicitGrantType; } }); +var password_grant_type_1 = require("./password-grant-type"); +Object.defineProperty(exports, "PasswordGrantType", { enumerable: true, get: function () { return password_grant_type_1.PasswordGrantType; } }); +var refresh_token_grant_type_1 = require("./refresh-token-grant-type"); +Object.defineProperty(exports, "RefreshTokenGrantType", { enumerable: true, get: function () { return refresh_token_grant_type_1.RefreshTokenGrantType; } }); +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/lib/grant-types/index.js.map b/dist/lib/grant-types/index.js.map new file mode 100644 index 000000000..ff7767673 --- /dev/null +++ b/dist/lib/grant-types/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../lib/grant-types/index.ts"],"names":[],"mappings":";;AAAA,6DAA0D;AAAjD,wHAAA,iBAAiB,OAAA;AAC1B,iFAA6E;AAApE,2IAAA,0BAA0B,OAAA;AACnC,iFAA6E;AAApE,2IAAA,0BAA0B,OAAA;AACnC,6DAA0D;AAAjD,wHAAA,iBAAiB,OAAA;AAC1B,6DAA0D;AAAjD,wHAAA,iBAAiB,OAAA;AAC1B,uEAAmE;AAA1D,iIAAA,qBAAqB,OAAA"} \ No newline at end of file diff --git a/dist/lib/grant-types/password-grant-type.d.ts b/dist/lib/grant-types/password-grant-type.d.ts new file mode 100644 index 000000000..774b29233 --- /dev/null +++ b/dist/lib/grant-types/password-grant-type.d.ts @@ -0,0 +1,9 @@ +import { AbstractGrantType } from '.'; +import { Client, Token, User } from '../interfaces'; +import { Request } from '../request'; +export declare class PasswordGrantType extends AbstractGrantType { + constructor(options?: any); + handle(request: any, client: any): Promise; + getUser(request: Request): Promise; + saveToken(user: User, client: Client, scope: string): Promise; +} diff --git a/dist/lib/grant-types/password-grant-type.js b/dist/lib/grant-types/password-grant-type.js new file mode 100644 index 000000000..ee4db5396 --- /dev/null +++ b/dist/lib/grant-types/password-grant-type.js @@ -0,0 +1,67 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.PasswordGrantType = void 0; +const _1 = require("."); +const errors_1 = require("../errors"); +const is = require("../validator/is"); +class PasswordGrantType extends _1.AbstractGrantType { + constructor(options = {}) { + super(options); + if (!options.model) { + throw new errors_1.InvalidArgumentError('Missing parameter: `model`'); + } + if (!options.model.getUser) { + throw new errors_1.InvalidArgumentError('Invalid argument: model does not implement `getUser()`'); + } + if (!options.model.saveToken) { + throw new errors_1.InvalidArgumentError('Invalid argument: model does not implement `saveToken()`'); + } + } + async handle(request, client) { + if (!request) { + throw new errors_1.InvalidArgumentError('Missing parameter: `request`'); + } + if (!client) { + throw new errors_1.InvalidArgumentError('Missing parameter: `client`'); + } + const scope = this.getScope(request); + const user = await this.getUser(request); + return this.saveToken(user, client, scope); + } + async getUser(request) { + if (!request.body.username) { + throw new errors_1.InvalidRequestError('Missing parameter: `username`'); + } + if (!request.body.password) { + throw new errors_1.InvalidRequestError('Missing parameter: `password`'); + } + if (!is.uchar(request.body.username)) { + throw new errors_1.InvalidRequestError('Invalid parameter: `username`'); + } + if (!is.uchar(request.body.password)) { + throw new errors_1.InvalidRequestError('Invalid parameter: `password`'); + } + const user = await this.model.getUser(request.body.username, request.body.password); + if (!user) { + throw new errors_1.InvalidGrantError('Invalid grant: user credentials are invalid'); + } + return user; + } + async saveToken(user, client, scope) { + const accessScope = await this.validateScope(user, client, scope); + const accessToken = await this.generateAccessToken(client, user, scope); + const refreshToken = await this.generateRefreshToken(client, user, scope); + const accessTokenExpiresAt = this.getAccessTokenExpiresAt(); + const refreshTokenExpiresAt = this.getRefreshTokenExpiresAt(); + const token = { + accessToken, + accessTokenExpiresAt, + refreshToken, + refreshTokenExpiresAt, + scope: accessScope, + }; + return this.model.saveToken(token, client, user); + } +} +exports.PasswordGrantType = PasswordGrantType; +//# sourceMappingURL=password-grant-type.js.map \ No newline at end of file diff --git a/dist/lib/grant-types/password-grant-type.js.map b/dist/lib/grant-types/password-grant-type.js.map new file mode 100644 index 000000000..cac30731d --- /dev/null +++ b/dist/lib/grant-types/password-grant-type.js.map @@ -0,0 +1 @@ +{"version":3,"file":"password-grant-type.js","sourceRoot":"","sources":["../../../lib/grant-types/password-grant-type.ts"],"names":[],"mappings":";;;AAAA,wBAAsC;AACtC,sCAImB;AAGnB,sCAAsC;AAEtC,MAAa,iBAAkB,SAAQ,oBAAiB;IACtD,YAAY,UAAe,EAAE;QAC3B,KAAK,CAAC,OAAO,CAAC,CAAC;QAEf,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE;YAClB,MAAM,IAAI,6BAAoB,CAAC,4BAA4B,CAAC,CAAC;SAC9D;QAED,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE;YAC1B,MAAM,IAAI,6BAAoB,CAC5B,wDAAwD,CACzD,CAAC;SACH;QAED,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE;YAC5B,MAAM,IAAI,6BAAoB,CAC5B,0DAA0D,CAC3D,CAAC;SACH;IACH,CAAC;IAQD,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM;QAC1B,IAAI,CAAC,OAAO,EAAE;YACZ,MAAM,IAAI,6BAAoB,CAAC,8BAA8B,CAAC,CAAC;SAChE;QAED,IAAI,CAAC,MAAM,EAAE;YACX,MAAM,IAAI,6BAAoB,CAAC,6BAA6B,CAAC,CAAC;SAC/D;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACrC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAEzC,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;IAC7C,CAAC;IAMD,KAAK,CAAC,OAAO,CAAC,OAAgB;QAC5B,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE;YAC1B,MAAM,IAAI,4BAAmB,CAAC,+BAA+B,CAAC,CAAC;SAChE;QAED,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE;YAC1B,MAAM,IAAI,4BAAmB,CAAC,+BAA+B,CAAC,CAAC;SAChE;QAED,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE;YACpC,MAAM,IAAI,4BAAmB,CAAC,+BAA+B,CAAC,CAAC;SAChE;QAED,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE;YACpC,MAAM,IAAI,4BAAmB,CAAC,+BAA+B,CAAC,CAAC;SAChE;QAED,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CACnC,OAAO,CAAC,IAAI,CAAC,QAAQ,EACrB,OAAO,CAAC,IAAI,CAAC,QAAQ,CACtB,CAAC;QACF,IAAI,CAAC,IAAI,EAAE;YACT,MAAM,IAAI,0BAAiB,CACzB,6CAA6C,CAC9C,CAAC;SACH;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAMD,KAAK,CAAC,SAAS,CAAC,IAAU,EAAE,MAAc,EAAE,KAAa;QACvD,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;QAClE,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;QACxE,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,oBAAoB,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;QAC1E,MAAM,oBAAoB,GAAG,IAAI,CAAC,uBAAuB,EAAE,CAAC;QAC5D,MAAM,qBAAqB,GAAG,IAAI,CAAC,wBAAwB,EAAE,CAAC;QAE9D,MAAM,KAAK,GAAG;YACZ,WAAW;YACX,oBAAoB;YACpB,YAAY;YACZ,qBAAqB;YACrB,KAAK,EAAE,WAAW;SACV,CAAC;QAEX,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;IACnD,CAAC;CACF;AAjGD,8CAiGC"} \ No newline at end of file diff --git a/dist/lib/grant-types/refresh-token-grant-type.d.ts b/dist/lib/grant-types/refresh-token-grant-type.d.ts new file mode 100644 index 000000000..96157b069 --- /dev/null +++ b/dist/lib/grant-types/refresh-token-grant-type.d.ts @@ -0,0 +1,10 @@ +import { AbstractGrantType } from '.'; +import { Client, RefreshToken, User } from '../interfaces'; +import { Request } from '../request'; +export declare class RefreshTokenGrantType extends AbstractGrantType { + constructor(options?: any); + handle(request: Request, client: Client): Promise; + getRefreshToken(request: Request, client: Client): Promise; + revokeToken(token: RefreshToken): Promise; + saveToken(user: User, client: Client, scope: string): Promise; +} diff --git a/dist/lib/grant-types/refresh-token-grant-type.js b/dist/lib/grant-types/refresh-token-grant-type.js new file mode 100644 index 000000000..f4ca9bfa9 --- /dev/null +++ b/dist/lib/grant-types/refresh-token-grant-type.js @@ -0,0 +1,93 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.RefreshTokenGrantType = void 0; +const _1 = require("."); +const errors_1 = require("../errors"); +const is = require("../validator/is"); +class RefreshTokenGrantType extends _1.AbstractGrantType { + constructor(options = {}) { + super(options); + if (!options.model) { + throw new errors_1.InvalidArgumentError('Missing parameter: `model`'); + } + if (!options.model.getRefreshToken) { + throw new errors_1.InvalidArgumentError('Invalid argument: model does not implement `getRefreshToken()`'); + } + if (!options.model.revokeToken) { + throw new errors_1.InvalidArgumentError('Invalid argument: model does not implement `revokeToken()`'); + } + if (!options.model.saveToken) { + throw new errors_1.InvalidArgumentError('Invalid argument: model does not implement `saveToken()`'); + } + } + async handle(request, client) { + if (!request) { + throw new errors_1.InvalidArgumentError('Missing parameter: `request`'); + } + if (!client) { + throw new errors_1.InvalidArgumentError('Missing parameter: `client`'); + } + const token = await this.getRefreshToken(request, client); + await this.revokeToken(token); + return this.saveToken(token.user, client, token.scope); + } + async getRefreshToken(request, client) { + if (!request.body.refresh_token) { + throw new errors_1.InvalidRequestError('Missing parameter: `refresh_token`'); + } + if (!is.vschar(request.body.refresh_token)) { + throw new errors_1.InvalidRequestError('Invalid parameter: `refresh_token`'); + } + const token = await this.model.getRefreshToken(request.body.refresh_token); + if (!token) { + throw new errors_1.InvalidGrantError('Invalid grant: refresh token is invalid'); + } + if (!token.client) { + throw new errors_1.ServerError('Server error: `getRefreshToken()` did not return a `client` object'); + } + if (!token.user) { + throw new errors_1.ServerError('Server error: `getRefreshToken()` did not return a `user` object'); + } + if (token.client.id !== client.id) { + throw new errors_1.InvalidGrantError('Invalid grant: refresh token is invalid'); + } + if (token.refreshTokenExpiresAt && + !(token.refreshTokenExpiresAt instanceof Date)) { + throw new errors_1.ServerError('Server error: `refreshTokenExpiresAt` must be a Date instance'); + } + if (token.refreshTokenExpiresAt && + token.refreshTokenExpiresAt.getTime() < Date.now()) { + throw new errors_1.InvalidGrantError('Invalid grant: refresh token has expired'); + } + return token; + } + async revokeToken(token) { + if (this.alwaysIssueNewRefreshToken === false) { + return token; + } + const status = await this.model.revokeToken(token); + if (!status) { + throw new errors_1.InvalidGrantError('Invalid grant: refresh token is invalid'); + } + return token; + } + async saveToken(user, client, scope) { + const accessToken = await this.generateAccessToken(client, user, scope); + const refreshToken = await this.generateRefreshToken(client, user, scope); + const accessTokenExpiresAt = this.getAccessTokenExpiresAt(); + const refreshTokenExpiresAt = this.getRefreshTokenExpiresAt(); + const token = { + accessToken, + accessTokenExpiresAt, + scope, + }; + if (this.alwaysIssueNewRefreshToken !== false) { + token.refreshToken = refreshToken; + token.refreshTokenExpiresAt = refreshTokenExpiresAt; + } + const savedToken = await this.model.saveToken(token, client, user); + return savedToken; + } +} +exports.RefreshTokenGrantType = RefreshTokenGrantType; +//# sourceMappingURL=refresh-token-grant-type.js.map \ No newline at end of file diff --git a/dist/lib/grant-types/refresh-token-grant-type.js.map b/dist/lib/grant-types/refresh-token-grant-type.js.map new file mode 100644 index 000000000..a0c545ea2 --- /dev/null +++ b/dist/lib/grant-types/refresh-token-grant-type.js.map @@ -0,0 +1 @@ +{"version":3,"file":"refresh-token-grant-type.js","sourceRoot":"","sources":["../../../lib/grant-types/refresh-token-grant-type.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/lib/handlers/authenticate-handler.d.ts b/dist/lib/handlers/authenticate-handler.d.ts new file mode 100644 index 000000000..bc8c4ab7c --- /dev/null +++ b/dist/lib/handlers/authenticate-handler.d.ts @@ -0,0 +1,20 @@ +import { Model, Token } from '../interfaces'; +import { Request } from '../request'; +import { Response } from '../response'; +export declare class AuthenticateHandler { + addAcceptedScopesHeader: any; + addAuthorizedScopesHeader: any; + allowBearerTokensInQueryString: any; + model: Model; + scope: any; + constructor(options?: any); + handle(request: Request, response: Response): Promise; + getTokenFromRequest(request: Request): any; + getTokenFromRequestHeader(request: Request): any; + getTokenFromRequestQuery(request: Request): any; + getTokenFromRequestBody(request: Request): any; + getAccessToken(token: string): Promise; + validateAccessToken(accessToken: Token): Token; + verifyScope(accessToken: Token): Promise; + updateResponse(response: Response, accessToken: Token): void; +} diff --git a/dist/lib/handlers/authenticate-handler.js b/dist/lib/handlers/authenticate-handler.js new file mode 100644 index 000000000..896b591df --- /dev/null +++ b/dist/lib/handlers/authenticate-handler.js @@ -0,0 +1,136 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AuthenticateHandler = void 0; +const errors_1 = require("../errors"); +const request_1 = require("../request"); +const response_1 = require("../response"); +class AuthenticateHandler { + constructor(options = {}) { + if (!options.model) { + throw new errors_1.InvalidArgumentError('Missing parameter: `model`'); + } + if (!options.model.getAccessToken) { + throw new errors_1.InvalidArgumentError('Invalid argument: model does not implement `getAccessToken()`'); + } + if (options.scope && options.addAcceptedScopesHeader === undefined) { + throw new errors_1.InvalidArgumentError('Missing parameter: `addAcceptedScopesHeader`'); + } + if (options.scope && options.addAuthorizedScopesHeader === undefined) { + throw new errors_1.InvalidArgumentError('Missing parameter: `addAuthorizedScopesHeader`'); + } + if (options.scope && !options.model.verifyScope) { + throw new errors_1.InvalidArgumentError('Invalid argument: model does not implement `verifyScope()`'); + } + this.addAcceptedScopesHeader = options.addAcceptedScopesHeader; + this.addAuthorizedScopesHeader = options.addAuthorizedScopesHeader; + this.allowBearerTokensInQueryString = + options.allowBearerTokensInQueryString; + this.model = options.model; + this.scope = options.scope; + } + async handle(request, response) { + if (!(request instanceof request_1.Request)) { + throw new errors_1.InvalidArgumentError('Invalid argument: `request` must be an instance of Request'); + } + if (!(response instanceof response_1.Response)) { + throw new errors_1.InvalidArgumentError('Invalid argument: `response` must be an instance of Response'); + } + this.model.request = request; + try { + let token = await this.getTokenFromRequest(request); + token = await this.getAccessToken(token); + this.validateAccessToken(token); + if (this.scope) { + await this.verifyScope(token); + } + this.updateResponse(response, token); + return token; + } + catch (e) { + if (e instanceof errors_1.UnauthorizedRequestError) { + response.set('WWW-Authenticate', 'Bearer realm="Service"'); + } + if (!(e instanceof errors_1.OAuthError)) { + throw new errors_1.ServerError(e); + } + throw e; + } + } + getTokenFromRequest(request) { + const headerToken = request.get('Authorization'); + const queryToken = request.query.access_token; + const bodyToken = request.body.access_token; + if ([headerToken, queryToken, bodyToken].filter(Boolean).length > 1) { + throw new errors_1.InvalidRequestError('Invalid request: only one authentication method is allowed'); + } + if (headerToken) { + return this.getTokenFromRequestHeader(request); + } + if (queryToken) { + return this.getTokenFromRequestQuery(request); + } + if (bodyToken) { + return this.getTokenFromRequestBody(request); + } + throw new errors_1.UnauthorizedRequestError('Unauthorized request: no authentication given'); + } + getTokenFromRequestHeader(request) { + const token = request.get('Authorization'); + const matches = token.match(/Bearer\s(\S+)/); + if (!matches) { + throw new errors_1.InvalidRequestError('Invalid request: malformed authorization header'); + } + return matches[1]; + } + getTokenFromRequestQuery(request) { + if (!this.allowBearerTokensInQueryString) { + throw new errors_1.InvalidRequestError('Invalid request: do not send bearer tokens in query URLs'); + } + return request.query.access_token; + } + getTokenFromRequestBody(request) { + if (request.method === 'GET') { + throw new errors_1.InvalidRequestError('Invalid request: token may not be passed in the body when using the GET verb'); + } + if (!request.is('application/x-www-form-urlencoded')) { + throw new errors_1.InvalidRequestError('Invalid request: content must be application/x-www-form-urlencoded'); + } + return request.body.access_token; + } + async getAccessToken(token) { + const accessToken = await this.model.getAccessToken(token); + if (!accessToken) { + throw new errors_1.InvalidTokenError('Invalid token: access token is invalid'); + } + if (!accessToken.user) { + throw new errors_1.ServerError('Server error: `getAccessToken()` did not return a `user` object'); + } + return accessToken; + } + validateAccessToken(accessToken) { + if (!(accessToken.accessTokenExpiresAt instanceof Date)) { + throw new errors_1.ServerError('Server error: `accessTokenExpiresAt` must be a Date instance'); + } + if (accessToken.accessTokenExpiresAt.getTime() < Date.now()) { + throw new errors_1.InvalidTokenError('Invalid token: access token has expired'); + } + return accessToken; + } + async verifyScope(accessToken) { + const scope = await this.model.verifyScope(accessToken, this.scope); + if (!scope) { + throw new errors_1.InsufficientScopeError('Insufficient scope: authorized scope is insufficient'); + } + return scope; + } + updateResponse(response, accessToken) { + if (this.scope && this.addAcceptedScopesHeader) { + response.set('X-Accepted-OAuth-Scopes', this.scope); + } + if (this.scope && this.addAuthorizedScopesHeader) { + response.set('X-OAuth-Scopes', accessToken.scope); + } + } +} +exports.AuthenticateHandler = AuthenticateHandler; +//# sourceMappingURL=authenticate-handler.js.map \ No newline at end of file diff --git a/dist/lib/handlers/authenticate-handler.js.map b/dist/lib/handlers/authenticate-handler.js.map new file mode 100644 index 000000000..1fb680d73 --- /dev/null +++ b/dist/lib/handlers/authenticate-handler.js.map @@ -0,0 +1 @@ +{"version":3,"file":"authenticate-handler.js","sourceRoot":"","sources":["../../../lib/handlers/authenticate-handler.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/lib/handlers/authorize-handler.d.ts b/dist/lib/handlers/authorize-handler.d.ts new file mode 100644 index 000000000..4908f297b --- /dev/null +++ b/dist/lib/handlers/authorize-handler.d.ts @@ -0,0 +1,24 @@ +/// +import * as url from 'url'; +import { Client, Model, User } from '../interfaces'; +import { Request } from '../request'; +import { Response } from '../response'; +import { CodeResponseType, TokenResponseType } from '../response-types'; +export declare class AuthorizeHandler { + options: any; + allowEmptyState: boolean; + authenticateHandler: any; + model: Model; + constructor(options?: any); + handle(request: Request, response: Response): Promise; + getClient(request: Request): Promise; + validateScope(user: User, client: Client, scope: string): Promise; + getScope(request: Request): any; + getState(request: Request): any; + getUser(request: Request, response: Response): Promise; + getRedirectUri(request: Request, client: Client): any; + getResponseType(request: Request, client: Client): any; + buildSuccessRedirectUri(redirectUri: string, responseType: CodeResponseType | TokenResponseType): any; + buildErrorRedirectUri(redirectUri: any, responseType: CodeResponseType | TokenResponseType, error: Error): url.UrlWithParsedQuery; + updateResponse(response: Response, redirectUri: any, responseType: CodeResponseType | TokenResponseType, state: any): void; +} diff --git a/dist/lib/handlers/authorize-handler.js b/dist/lib/handlers/authorize-handler.js new file mode 100644 index 000000000..8c7e4bfb2 --- /dev/null +++ b/dist/lib/handlers/authorize-handler.js @@ -0,0 +1,195 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AuthorizeHandler = void 0; +const url = require("url"); +const _1 = require("."); +const errors_1 = require("../errors"); +const request_1 = require("../request"); +const response_1 = require("../response"); +const response_types_1 = require("../response-types"); +const fn_1 = require("../utils/fn"); +const is = require("../validator/is"); +const responseTypes = { + code: response_types_1.CodeResponseType, + token: response_types_1.TokenResponseType, +}; +class AuthorizeHandler { + constructor(options = {}) { + if (options.authenticateHandler && !options.authenticateHandler.handle) { + throw new errors_1.InvalidArgumentError('Invalid argument: authenticateHandler does not implement `handle()`'); + } + if (!options.model) { + throw new errors_1.InvalidArgumentError('Missing parameter: `model`'); + } + if (!options.model.getClient) { + throw new errors_1.InvalidArgumentError('Invalid argument: model does not implement `getClient()`'); + } + this.options = options; + this.allowEmptyState = options.allowEmptyState; + this.authenticateHandler = + options.authenticateHandler || new _1.AuthenticateHandler(options); + this.model = options.model; + } + async handle(request, response) { + if (!(request instanceof request_1.Request)) { + throw new errors_1.InvalidArgumentError('Invalid argument: `request` must be an instance of Request'); + } + if (!(response instanceof response_1.Response)) { + throw new errors_1.InvalidArgumentError('Invalid argument: `response` must be an instance of Response'); + } + if (request.query.allowed === 'false') { + throw new errors_1.AccessDeniedError('Access denied: user denied access to application'); + } + this.model.request = request; + const client = await this.getClient(request); + const user = await this.getUser(request, response); + let scope; + let state; + let RequestedResponseType; + let responseType; + const uri = this.getRedirectUri(request, client); + try { + const requestedScope = this.getScope(request); + const validScope = await this.validateScope(user, client, requestedScope); + scope = validScope; + state = this.getState(request); + RequestedResponseType = this.getResponseType(request, client); + responseType = new RequestedResponseType(this.options); + const codeOrAccessToken = await responseType.handle(request, client, user, uri, scope); + const redirectUri = this.buildSuccessRedirectUri(uri, responseType); + this.updateResponse(response, redirectUri, responseType, state); + return codeOrAccessToken; + } + catch (e) { + if (!(e instanceof errors_1.OAuthError)) { + e = new errors_1.ServerError(e); + } + const redirectUri = this.buildErrorRedirectUri(uri, responseType, e); + this.updateResponse(response, redirectUri, responseType, state); + throw e; + } + } + async getClient(request) { + const clientId = request.body.client_id || request.query.client_id; + if (!clientId) { + throw new errors_1.InvalidRequestError('Missing parameter: `client_id`'); + } + if (!is.vschar(clientId)) { + throw new errors_1.InvalidRequestError('Invalid parameter: `client_id`'); + } + const redirectUri = request.body.redirect_uri || request.query.redirect_uri; + if (redirectUri && !is.uri(redirectUri)) { + throw new errors_1.InvalidRequestError('Invalid request: `redirect_uri` is not a valid URI'); + } + const client = await this.model.getClient(clientId); + if (!client) { + throw new errors_1.InvalidClientError('Invalid client: client credentials are invalid'); + } + if (!client.grants) { + throw new errors_1.InvalidClientError('Invalid client: missing client `grants`'); + } + const responseType = request.body.response_type || request.query.response_type; + const requestedGrantType = responseType === 'token' ? 'implicit' : 'authorization_code'; + if (!client.grants.includes(requestedGrantType)) { + throw new errors_1.UnauthorizedClientError('Unauthorized client: `grant_type` is invalid'); + } + if (!client.redirectUris || client.redirectUris.length === 0) { + throw new errors_1.InvalidClientError('Invalid client: missing client `redirectUri`'); + } + if (redirectUri && !client.redirectUris.includes(redirectUri)) { + throw new errors_1.InvalidClientError('Invalid client: `redirect_uri` does not match client value'); + } + return client; + } + async validateScope(user, client, scope) { + if (this.model.validateScope) { + const validatedScope = await this.model.validateScope(user, client, scope); + if (!validatedScope) { + throw new errors_1.InvalidScopeError('Invalid scope: Requested scope is invalid'); + } + return validatedScope; + } + return scope; + } + getScope(request) { + const scope = request.body.scope || request.query.scope; + if (!is.nqschar(scope)) { + throw new errors_1.InvalidScopeError('Invalid parameter: `scope`'); + } + return scope; + } + getState(request) { + const state = request.body.state || request.query.state; + if (!this.allowEmptyState && !state) { + throw new errors_1.InvalidRequestError('Missing parameter: `state`'); + } + if (!is.vschar(state)) { + throw new errors_1.InvalidRequestError('Invalid parameter: `state`'); + } + return state; + } + async getUser(request, response) { + if (this.authenticateHandler instanceof _1.AuthenticateHandler) { + const data = await this.authenticateHandler.handle(request, response); + return data.user; + } + const user = await this.authenticateHandler.handle(request, response); + if (!user) { + throw new errors_1.ServerError('Server error: `handle()` did not return a `user` object'); + } + return user; + } + getRedirectUri(request, client) { + return (request.body.redirect_uri || + request.query.redirect_uri || + client.redirectUris[0]); + } + getResponseType(request, client) { + const responseType = request.body.response_type || request.query.response_type; + if (!responseType) { + throw new errors_1.InvalidRequestError('Missing parameter: `response_type`'); + } + if (!fn_1.hasOwnProperty(responseTypes, responseType)) { + throw new errors_1.UnsupportedResponseTypeError('Unsupported response type: `response_type` is not supported'); + } + if (responseType === 'token' && + (!client || !client.grants.includes('implicit'))) { + throw new errors_1.UnauthorizedClientError('Unauthorized client: `grant_type` is invalid'); + } + return responseTypes[responseType]; + } + buildSuccessRedirectUri(redirectUri, responseType) { + const uri = url.parse(redirectUri); + return responseType.buildRedirectUri(uri); + } + buildErrorRedirectUri(redirectUri, responseType, error) { + let uri = url.parse(redirectUri, true); + if (responseType) { + uri = responseType.setRedirectUriParam(uri, 'error', error.name); + if (error.message) { + uri = responseType.setRedirectUriParam(uri, 'error_description', error.message); + } + } + else { + uri.query = { + error: error.name, + }; + if (error.message) { + uri.query.error_description = error.message; + } + } + return uri; + } + updateResponse(response, redirectUri, responseType, state) { + if (responseType && state) { + redirectUri = responseType.setRedirectUriParam(redirectUri, 'state', state); + } + else if (state) { + redirectUri.query = redirectUri.query || {}; + redirectUri.query.state = state; + } + response.redirect(url.format(redirectUri)); + } +} +exports.AuthorizeHandler = AuthorizeHandler; +//# sourceMappingURL=authorize-handler.js.map \ No newline at end of file diff --git a/dist/lib/handlers/authorize-handler.js.map b/dist/lib/handlers/authorize-handler.js.map new file mode 100644 index 000000000..049427249 --- /dev/null +++ b/dist/lib/handlers/authorize-handler.js.map @@ -0,0 +1 @@ +{"version":3,"file":"authorize-handler.js","sourceRoot":"","sources":["../../../lib/handlers/authorize-handler.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/lib/handlers/index.d.ts b/dist/lib/handlers/index.d.ts new file mode 100644 index 000000000..7bdf800ee --- /dev/null +++ b/dist/lib/handlers/index.d.ts @@ -0,0 +1,4 @@ +export { AuthenticateHandler } from './authenticate-handler'; +export { AuthorizeHandler } from './authorize-handler'; +export { RevokeHandler } from './revoke-handler'; +export { TokenHandler } from './token-handler'; diff --git a/dist/lib/handlers/index.js b/dist/lib/handlers/index.js new file mode 100644 index 000000000..37166b30b --- /dev/null +++ b/dist/lib/handlers/index.js @@ -0,0 +1,11 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var authenticate_handler_1 = require("./authenticate-handler"); +Object.defineProperty(exports, "AuthenticateHandler", { enumerable: true, get: function () { return authenticate_handler_1.AuthenticateHandler; } }); +var authorize_handler_1 = require("./authorize-handler"); +Object.defineProperty(exports, "AuthorizeHandler", { enumerable: true, get: function () { return authorize_handler_1.AuthorizeHandler; } }); +var revoke_handler_1 = require("./revoke-handler"); +Object.defineProperty(exports, "RevokeHandler", { enumerable: true, get: function () { return revoke_handler_1.RevokeHandler; } }); +var token_handler_1 = require("./token-handler"); +Object.defineProperty(exports, "TokenHandler", { enumerable: true, get: function () { return token_handler_1.TokenHandler; } }); +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/lib/handlers/index.js.map b/dist/lib/handlers/index.js.map new file mode 100644 index 000000000..ff0688081 --- /dev/null +++ b/dist/lib/handlers/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../lib/handlers/index.ts"],"names":[],"mappings":";;AAAA,+DAA6D;AAApD,2HAAA,mBAAmB,OAAA;AAC5B,yDAAuD;AAA9C,qHAAA,gBAAgB,OAAA;AACzB,mDAAiD;AAAxC,+GAAA,aAAa,OAAA;AACtB,iDAA+C;AAAtC,6GAAA,YAAY,OAAA"} \ No newline at end of file diff --git a/dist/lib/handlers/revoke-handler.d.ts b/dist/lib/handlers/revoke-handler.d.ts new file mode 100644 index 000000000..6fae39fae --- /dev/null +++ b/dist/lib/handlers/revoke-handler.d.ts @@ -0,0 +1,20 @@ +import { OAuthError } from '../errors'; +import { Client, Model } from '../interfaces'; +import { Request } from '../request'; +import { Response } from '../response'; +export declare class RevokeHandler { + model: Model; + constructor(options?: any); + handle(request: Request, response: Response): Promise; + handleRevokeToken(request: Request, client: Client): Promise; + getClient(request: Request, response: Response): Promise; + getClientCredentials(request: Request): { + clientId: any; + clientSecret: any; + }; + getTokenFromRequest(request: Request): any; + getRefreshToken(token: any, client: Client): Promise; + getAccessToken(token: string, client: Client): Promise; + revokeToken(token: any): Promise; + updateErrorResponse(response: Response, error: OAuthError): void; +} diff --git a/dist/lib/handlers/revoke-handler.js b/dist/lib/handlers/revoke-handler.js new file mode 100644 index 000000000..abfe4a5f2 --- /dev/null +++ b/dist/lib/handlers/revoke-handler.js @@ -0,0 +1,190 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.RevokeHandler = void 0; +const auth = require("basic-auth"); +const errors_1 = require("../errors"); +const request_1 = require("../request"); +const response_1 = require("../response"); +const fn_1 = require("../utils/fn"); +const is = require("../validator/is"); +class RevokeHandler { + constructor(options = {}) { + if (!options.model) { + throw new errors_1.InvalidArgumentError('Missing parameter: `model`'); + } + if (!options.model.getClient) { + throw new errors_1.InvalidArgumentError('Invalid argument: model does not implement `getClient()`'); + } + if (!options.model.getRefreshToken) { + throw new errors_1.InvalidArgumentError('Invalid argument: model does not implement `getRefreshToken()`'); + } + if (!options.model.getAccessToken) { + throw new errors_1.InvalidArgumentError('Invalid argument: model does not implement `getAccessToken()`'); + } + if (!options.model.revokeToken) { + throw new errors_1.InvalidArgumentError('Invalid argument: model does not implement `revokeToken()`'); + } + this.model = options.model; + } + async handle(request, response) { + if (!(request instanceof request_1.Request)) { + throw new errors_1.InvalidArgumentError('Invalid argument: `request` must be an instance of Request'); + } + if (!(response instanceof response_1.Response)) { + throw new errors_1.InvalidArgumentError('Invalid argument: `response` must be an instance of Response'); + } + if (request.method !== 'POST') { + throw new errors_1.InvalidRequestError('Invalid request: method must be POST'); + } + if (!request.is('application/x-www-form-urlencoded')) { + throw new errors_1.InvalidRequestError('Invalid request: content must be application/x-www-form-urlencoded'); + } + this.model.request = request; + try { + const client = await this.getClient(request, response); + return this.handleRevokeToken(request, client); + } + catch (e) { + let error = e; + if (!(error instanceof errors_1.OAuthError)) { + error = new errors_1.ServerError(error); + } + if (!(error instanceof errors_1.InvalidTokenError)) { + this.updateErrorResponse(response, error); + } + throw error; + } + } + async handleRevokeToken(request, client) { + try { + let token = await this.getTokenFromRequest(request); + token = await fn_1.oneSuccess([ + this.getAccessToken(token, client), + this.getRefreshToken(token, client), + ]); + return this.revokeToken(token); + } + catch (errors) { + throw errors; + } + } + async getClient(request, response) { + const credentials = this.getClientCredentials(request); + if (!credentials.clientId) { + throw new errors_1.InvalidRequestError('Missing parameter: `client_id`'); + } + if (!credentials.clientSecret) { + throw new errors_1.InvalidRequestError('Missing parameter: `client_secret`'); + } + if (!is.vschar(credentials.clientId)) { + throw new errors_1.InvalidRequestError('Invalid parameter: `client_id`'); + } + if (!is.vschar(credentials.clientSecret)) { + throw new errors_1.InvalidRequestError('Invalid parameter: `client_secret`'); + } + try { + const client = await this.model.getClient(credentials.clientId, credentials.clientSecret); + if (!client) { + throw new errors_1.InvalidClientError('Invalid client: client is invalid'); + } + if (!client.grants) { + throw new errors_1.ServerError('Server error: missing client `grants`'); + } + if (!(client.grants instanceof Array)) { + throw new errors_1.ServerError('Server error: `grants` must be an array'); + } + return client; + } + catch (e) { + if (e instanceof errors_1.InvalidClientError && request.get('authorization')) { + response.set('WWW-Authenticate', 'Basic realm="Service"'); + throw new errors_1.InvalidClientError(e, { code: 401 }); + } + throw e; + } + } + getClientCredentials(request) { + const credentials = auth(request); + if (credentials) { + return { clientId: credentials.name, clientSecret: credentials.pass }; + } + if (request.body.client_id && request.body.client_secret) { + return { + clientId: request.body.client_id, + clientSecret: request.body.client_secret, + }; + } + throw new errors_1.InvalidClientError('Invalid client: cannot retrieve client credentials'); + } + getTokenFromRequest(request) { + const bodyToken = request.body.token; + if (!bodyToken) { + throw new errors_1.InvalidRequestError('Missing parameter: `token`'); + } + return bodyToken; + } + async getRefreshToken(token, client) { + const refreshToken = await this.model.getRefreshToken(token); + if (!refreshToken) { + throw new errors_1.InvalidTokenError('Invalid token: refresh token is invalid'); + } + if (!refreshToken.client) { + throw new errors_1.ServerError('Server error: `getRefreshToken()` did not return a `client` object'); + } + if (!refreshToken.user) { + throw new errors_1.ServerError('Server error: `getRefreshToken()` did not return a `user` object'); + } + if (refreshToken.client.id !== client.id) { + throw new errors_1.InvalidClientError('Invalid client: client is invalid'); + } + if (refreshToken.refreshTokenExpiresAt && + !(refreshToken.refreshTokenExpiresAt instanceof Date)) { + throw new errors_1.ServerError('Server error: `refreshTokenExpiresAt` must be a Date instance'); + } + if (refreshToken.refreshTokenExpiresAt && + refreshToken.refreshTokenExpiresAt.getTime() < Date.now()) { + throw new errors_1.InvalidTokenError('Invalid token: refresh token has expired'); + } + return refreshToken; + } + async getAccessToken(token, client) { + const accessToken = await this.model.getAccessToken(token); + if (!accessToken) { + throw new errors_1.InvalidTokenError('Invalid token: access token is invalid'); + } + if (!accessToken.client) { + throw new errors_1.ServerError('Server error: `getAccessToken()` did not return a `client` object'); + } + if (!accessToken.user) { + throw new errors_1.ServerError('Server error: `getAccessToken()` did not return a `user` object'); + } + if (accessToken.client.id !== client.id) { + throw new errors_1.InvalidClientError('Invalid client: client is invalid'); + } + if (accessToken.accessTokenExpiresAt && + !(accessToken.accessTokenExpiresAt instanceof Date)) { + throw new errors_1.ServerError('Server error: `expires` must be a Date instance'); + } + if (accessToken.accessTokenExpiresAt && + accessToken.accessTokenExpiresAt.getTime() < Date.now()) { + throw new errors_1.InvalidTokenError('Invalid token: access token has expired.'); + } + return accessToken; + } + async revokeToken(token) { + const revokedToken = await this.model.revokeToken(token); + if (!revokedToken) { + throw new errors_1.InvalidTokenError('Invalid token: token is invalid'); + } + return revokedToken; + } + updateErrorResponse(response, error) { + response.body = { + error: error.name, + error_description: error.message, + }; + response.status = error.code; + } +} +exports.RevokeHandler = RevokeHandler; +//# sourceMappingURL=revoke-handler.js.map \ No newline at end of file diff --git a/dist/lib/handlers/revoke-handler.js.map b/dist/lib/handlers/revoke-handler.js.map new file mode 100644 index 000000000..74e56caf0 --- /dev/null +++ b/dist/lib/handlers/revoke-handler.js.map @@ -0,0 +1 @@ +{"version":3,"file":"revoke-handler.js","sourceRoot":"","sources":["../../../lib/handlers/revoke-handler.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/lib/handlers/token-handler.d.ts b/dist/lib/handlers/token-handler.d.ts new file mode 100644 index 000000000..a44ec99ae --- /dev/null +++ b/dist/lib/handlers/token-handler.d.ts @@ -0,0 +1,33 @@ +import { OAuthError } from '../errors'; +import { Client, Model } from '../interfaces'; +import { Request } from '../request'; +import { Response } from '../response'; +import { BearerTokenType } from '../token-types'; +export declare class TokenHandler { + accessTokenLifetime: any; + grantTypes: { + [key: string]: any; + }; + model: Model; + refreshTokenLifetime: number; + allowExtendedTokenAttributes: boolean; + requireClientAuthentication: any; + alwaysIssueNewRefreshToken: boolean; + constructor(options?: any); + handle(request: Request, response: Response): Promise; + getClient(request: any, response: any): Promise; + getClientCredentials(request: Request): { + clientId: any; + clientSecret: any; + } | { + clientId: any; + clientSecret?: undefined; + }; + handleGrantType(request: Request, client: Client): Promise; + getAccessTokenLifetime(client: Client): any; + getRefreshTokenLifetime(client: Client): number; + getTokenType(model: any): BearerTokenType; + updateSuccessResponse(response: Response, tokenType: BearerTokenType): void; + updateErrorResponse(response: Response, error: OAuthError): void; + isClientAuthenticationRequired(grantType: string): any; +} diff --git a/dist/lib/handlers/token-handler.js b/dist/lib/handlers/token-handler.js new file mode 100644 index 000000000..b06aa398d --- /dev/null +++ b/dist/lib/handlers/token-handler.js @@ -0,0 +1,189 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TokenHandler = void 0; +const auth = require("basic-auth"); +const errors_1 = require("../errors"); +const grant_types_1 = require("../grant-types"); +const models_1 = require("../models"); +const request_1 = require("../request"); +const response_1 = require("../response"); +const token_types_1 = require("../token-types"); +const fn_1 = require("../utils/fn"); +const is = require("../validator/is"); +const grantTypes = { + authorization_code: grant_types_1.AuthorizationCodeGrantType, + client_credentials: grant_types_1.ClientCredentialsGrantType, + password: grant_types_1.PasswordGrantType, + refresh_token: grant_types_1.RefreshTokenGrantType, +}; +class TokenHandler { + constructor(options = {}) { + if (!options.accessTokenLifetime) { + throw new errors_1.InvalidArgumentError('Missing parameter: `accessTokenLifetime`'); + } + if (!options.model) { + throw new errors_1.InvalidArgumentError('Missing parameter: `model`'); + } + if (!options.refreshTokenLifetime) { + throw new errors_1.InvalidArgumentError('Missing parameter: `refreshTokenLifetime`'); + } + if (!options.model.getClient) { + throw new errors_1.InvalidArgumentError('Invalid argument: model does not implement `getClient()`'); + } + this.accessTokenLifetime = options.accessTokenLifetime; + this.grantTypes = Object.assign(Object.assign({}, grantTypes), options.extendedGrantTypes); + this.model = options.model; + this.refreshTokenLifetime = options.refreshTokenLifetime; + this.allowExtendedTokenAttributes = options.allowExtendedTokenAttributes; + this.requireClientAuthentication = + options.requireClientAuthentication || {}; + this.alwaysIssueNewRefreshToken = + options.alwaysIssueNewRefreshToken !== false; + } + async handle(request, response) { + if (!(request instanceof request_1.Request)) { + throw new errors_1.InvalidArgumentError('Invalid argument: `request` must be an instance of Request'); + } + if (!(response instanceof response_1.Response)) { + throw new errors_1.InvalidArgumentError('Invalid argument: `response` must be an instance of Response'); + } + if (request.method !== 'POST') { + throw new errors_1.InvalidRequestError('Invalid request: method must be POST'); + } + if (!request.is('application/x-www-form-urlencoded')) { + throw new errors_1.InvalidRequestError('Invalid request: content must be application/x-www-form-urlencoded'); + } + this.model.request = request; + try { + const client = await this.getClient(request, response); + const data = await this.handleGrantType(request, client); + const model = new models_1.TokenModel(data, { + allowExtendedTokenAttributes: this.allowExtendedTokenAttributes, + }); + const tokenType = this.getTokenType(model); + this.updateSuccessResponse(response, tokenType); + return data; + } + catch (e) { + if (!(e instanceof errors_1.OAuthError)) { + e = new errors_1.ServerError(e); + } + this.updateErrorResponse(response, e); + throw e; + } + } + async getClient(request, response) { + const credentials = this.getClientCredentials(request); + const grantType = request.body.grant_type; + if (!credentials.clientId) { + throw new errors_1.InvalidRequestError('Missing parameter: `client_id`'); + } + if (this.isClientAuthenticationRequired(grantType) && + !credentials.clientSecret) { + throw new errors_1.InvalidRequestError('Missing parameter: `client_secret`'); + } + if (!is.vschar(credentials.clientId)) { + throw new errors_1.InvalidRequestError('Invalid parameter: `client_id`'); + } + if (credentials.clientSecret && !is.vschar(credentials.clientSecret)) { + throw new errors_1.InvalidRequestError('Invalid parameter: `client_secret`'); + } + try { + const client = await this.model.getClient(credentials.clientId, credentials.clientSecret); + if (!client) { + throw new errors_1.InvalidClientError('Invalid client: client is invalid'); + } + if (!client.grants) { + throw new errors_1.ServerError('Server error: missing client `grants`'); + } + if (!(client.grants instanceof Array)) { + throw new errors_1.ServerError('Server error: `grants` must be an array'); + } + return client; + } + catch (e) { + if (e instanceof errors_1.InvalidClientError && request.get('authorization')) { + response.set('WWW-Authenticate', 'Basic realm="Service"'); + throw new errors_1.InvalidClientError(e, { code: 401 }); + } + throw e; + } + } + getClientCredentials(request) { + const credentials = auth(request); + const grantType = request.body.grant_type; + if (credentials) { + return { + clientId: credentials.name, + clientSecret: credentials.pass, + }; + } + if (request.body.client_id && request.body.client_secret) { + return { + clientId: request.body.client_id, + clientSecret: request.body.client_secret, + }; + } + if (!this.isClientAuthenticationRequired(grantType) && + request.body.client_id) { + return { clientId: request.body.client_id }; + } + throw new errors_1.InvalidClientError('Invalid client: cannot retrieve client credentials'); + } + async handleGrantType(request, client) { + const grantType = request.body.grant_type; + if (!grantType) { + throw new errors_1.InvalidRequestError('Missing parameter: `grant_type`'); + } + if (!is.nchar(grantType) && !is.uri(grantType)) { + throw new errors_1.InvalidRequestError('Invalid parameter: `grant_type`'); + } + if (!fn_1.hasOwnProperty(this.grantTypes, grantType)) { + throw new errors_1.UnsupportedGrantTypeError('Unsupported grant type: `grant_type` is invalid'); + } + if (!client.grants.includes(grantType)) { + throw new errors_1.UnauthorizedClientError('Unauthorized client: `grant_type` is invalid'); + } + const accessTokenLifetime = this.getAccessTokenLifetime(client); + const refreshTokenLifetime = this.getRefreshTokenLifetime(client); + const GrantType = this.grantTypes[grantType]; + const options = { + accessTokenLifetime, + model: this.model, + refreshTokenLifetime, + alwaysIssueNewRefreshToken: this.alwaysIssueNewRefreshToken, + }; + return new GrantType(options).handle(request, client); + } + getAccessTokenLifetime(client) { + return client.accessTokenLifetime || this.accessTokenLifetime; + } + getRefreshTokenLifetime(client) { + return client.refreshTokenLifetime || this.refreshTokenLifetime; + } + getTokenType(model) { + return new token_types_1.BearerTokenType(model.accessToken, model.accessTokenLifetime, model.refreshToken, model.scope, model.customAttributes); + } + updateSuccessResponse(response, tokenType) { + response.body = tokenType.valueOf(); + response.set('Cache-Control', 'no-store'); + response.set('Pragma', 'no-cache'); + } + updateErrorResponse(response, error) { + response.body = { + error: error.name, + error_description: error.message, + }; + response.status = error.code; + } + isClientAuthenticationRequired(grantType) { + if (Object.keys(this.requireClientAuthentication).length > 0) { + return typeof this.requireClientAuthentication[grantType] !== 'undefined' + ? this.requireClientAuthentication[grantType] + : true; + } + return true; + } +} +exports.TokenHandler = TokenHandler; +//# sourceMappingURL=token-handler.js.map \ No newline at end of file diff --git a/dist/lib/handlers/token-handler.js.map b/dist/lib/handlers/token-handler.js.map new file mode 100644 index 000000000..848bd3473 --- /dev/null +++ b/dist/lib/handlers/token-handler.js.map @@ -0,0 +1 @@ +{"version":3,"file":"token-handler.js","sourceRoot":"","sources":["../../../lib/handlers/token-handler.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/lib/interfaces/authorization-code.interface.d.ts b/dist/lib/interfaces/authorization-code.interface.d.ts new file mode 100644 index 000000000..8f213db29 --- /dev/null +++ b/dist/lib/interfaces/authorization-code.interface.d.ts @@ -0,0 +1,10 @@ +import { Client, User } from '.'; +export interface AuthorizationCode { + authorizationCode: string; + expiresAt: Date; + redirectUri: string; + scope?: string; + client: Client; + user: User; + [key: string]: any; +} diff --git a/dist/lib/interfaces/authorization-code.interface.js b/dist/lib/interfaces/authorization-code.interface.js new file mode 100644 index 000000000..533a50d21 --- /dev/null +++ b/dist/lib/interfaces/authorization-code.interface.js @@ -0,0 +1,3 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +//# sourceMappingURL=authorization-code.interface.js.map \ No newline at end of file diff --git a/dist/lib/interfaces/authorization-code.interface.js.map b/dist/lib/interfaces/authorization-code.interface.js.map new file mode 100644 index 000000000..fda743640 --- /dev/null +++ b/dist/lib/interfaces/authorization-code.interface.js.map @@ -0,0 +1 @@ +{"version":3,"file":"authorization-code.interface.js","sourceRoot":"","sources":["../../../lib/interfaces/authorization-code.interface.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/lib/interfaces/client.interface.d.ts b/dist/lib/interfaces/client.interface.d.ts new file mode 100644 index 000000000..fbcf0a11d --- /dev/null +++ b/dist/lib/interfaces/client.interface.d.ts @@ -0,0 +1,8 @@ +export interface Client { + id: string; + redirectUris?: string | string[]; + grants: string | string[]; + accessTokenLifetime?: number; + refreshTokenLifetime?: number; + [key: string]: any; +} diff --git a/dist/lib/interfaces/client.interface.js b/dist/lib/interfaces/client.interface.js new file mode 100644 index 000000000..3d9c47fba --- /dev/null +++ b/dist/lib/interfaces/client.interface.js @@ -0,0 +1,3 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +//# sourceMappingURL=client.interface.js.map \ No newline at end of file diff --git a/dist/lib/interfaces/client.interface.js.map b/dist/lib/interfaces/client.interface.js.map new file mode 100644 index 000000000..24f90a169 --- /dev/null +++ b/dist/lib/interfaces/client.interface.js.map @@ -0,0 +1 @@ +{"version":3,"file":"client.interface.js","sourceRoot":"","sources":["../../../lib/interfaces/client.interface.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/lib/interfaces/index.d.ts b/dist/lib/interfaces/index.d.ts new file mode 100644 index 000000000..a5cb2aeac --- /dev/null +++ b/dist/lib/interfaces/index.d.ts @@ -0,0 +1,6 @@ +export { AuthorizationCode } from './authorization-code.interface'; +export { Client } from './client.interface'; +export { Model } from './model.interface'; +export { RefreshToken } from './refresh-token.interface'; +export { Token } from './token.interface'; +export { User } from './user.interface'; diff --git a/dist/lib/interfaces/index.js b/dist/lib/interfaces/index.js new file mode 100644 index 000000000..aa219d8f2 --- /dev/null +++ b/dist/lib/interfaces/index.js @@ -0,0 +1,3 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/lib/interfaces/index.js.map b/dist/lib/interfaces/index.js.map new file mode 100644 index 000000000..f1336b90d --- /dev/null +++ b/dist/lib/interfaces/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../lib/interfaces/index.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/lib/interfaces/model.interface.d.ts b/dist/lib/interfaces/model.interface.d.ts new file mode 100644 index 000000000..1e723ba76 --- /dev/null +++ b/dist/lib/interfaces/model.interface.d.ts @@ -0,0 +1,38 @@ +import { AuthorizationCode, Client, RefreshToken, Token, User } from '.'; +import { Request } from '../request'; +export interface BaseModel { + request: Request; + generateAccessToken?(client: Client, user: User, scope: string): Promise; + getClient(clientId: string, clientSecret?: string): Promise; + saveToken(token: Token, client: Client, user: User): Promise; +} +export interface RequestAuthenticationModel { + getAccessToken(accessToken: string): Promise; + verifyScope(token: Token, scope: string): Promise; +} +export interface AuthorizationCodeModel extends BaseModel, RequestAuthenticationModel { + generateRefreshToken?(client: Client, user: User, scope: string): Promise; + generateAuthorizationCode?(client: Client, user: User, scope: string): Promise; + getAuthorizationCode(authorizationCode: string): Promise; + saveAuthorizationCode(code: AuthorizationCode, client: Client, user: User): Promise; + revokeAuthorizationCode(code: AuthorizationCode): Promise; + validateScope?(user: User, client: Client, scope: string): Promise; +} +export interface PasswordModel extends BaseModel, RequestAuthenticationModel { + generateRefreshToken?(client: Client, user: User, scope: string): Promise; + getUser(username: string, password: string): Promise; + validateScope?(user: User, client: Client, scope: string): Promise; +} +export interface RefreshTokenModel extends BaseModel, RequestAuthenticationModel { + generateRefreshToken?(client: Client, user: User, scope: string): Promise; + getRefreshToken(refreshToken: string): Promise; + revokeToken(token: RefreshToken | Token): Promise; +} +export interface ClientCredentialsModel extends BaseModel, RequestAuthenticationModel { + getUserFromClient(client: Client): Promise; + validateScope?(user: User, client: Client, scope: string): Promise; +} +export interface ExtensionModel extends BaseModel, RequestAuthenticationModel { +} +export interface Model extends BaseModel, RequestAuthenticationModel, AuthorizationCodeModel, PasswordModel, RefreshTokenModel, ClientCredentialsModel { +} diff --git a/dist/lib/interfaces/model.interface.js b/dist/lib/interfaces/model.interface.js new file mode 100644 index 000000000..06ebf2f67 --- /dev/null +++ b/dist/lib/interfaces/model.interface.js @@ -0,0 +1,3 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +//# sourceMappingURL=model.interface.js.map \ No newline at end of file diff --git a/dist/lib/interfaces/model.interface.js.map b/dist/lib/interfaces/model.interface.js.map new file mode 100644 index 000000000..0d99aa485 --- /dev/null +++ b/dist/lib/interfaces/model.interface.js.map @@ -0,0 +1 @@ +{"version":3,"file":"model.interface.js","sourceRoot":"","sources":["../../../lib/interfaces/model.interface.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/lib/interfaces/refresh-token.interface.d.ts b/dist/lib/interfaces/refresh-token.interface.d.ts new file mode 100644 index 000000000..a56bf4b24 --- /dev/null +++ b/dist/lib/interfaces/refresh-token.interface.d.ts @@ -0,0 +1,9 @@ +import { Client, User } from '.'; +export interface RefreshToken { + refreshToken: string; + refreshTokenExpiresAt?: Date; + scope?: string; + client: Client; + user: User; + [key: string]: any; +} diff --git a/dist/lib/interfaces/refresh-token.interface.js b/dist/lib/interfaces/refresh-token.interface.js new file mode 100644 index 000000000..e1759bcca --- /dev/null +++ b/dist/lib/interfaces/refresh-token.interface.js @@ -0,0 +1,3 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +//# sourceMappingURL=refresh-token.interface.js.map \ No newline at end of file diff --git a/dist/lib/interfaces/refresh-token.interface.js.map b/dist/lib/interfaces/refresh-token.interface.js.map new file mode 100644 index 000000000..01c0064e7 --- /dev/null +++ b/dist/lib/interfaces/refresh-token.interface.js.map @@ -0,0 +1 @@ +{"version":3,"file":"refresh-token.interface.js","sourceRoot":"","sources":["../../../lib/interfaces/refresh-token.interface.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/lib/interfaces/token.interface.d.ts b/dist/lib/interfaces/token.interface.d.ts new file mode 100644 index 000000000..c3d571a74 --- /dev/null +++ b/dist/lib/interfaces/token.interface.d.ts @@ -0,0 +1,11 @@ +import { Client, User } from '.'; +export interface Token { + accessToken: string; + accessTokenExpiresAt?: Date; + refreshToken?: string; + refreshTokenExpiresAt?: Date; + scope?: string; + client: Client; + user: User; + [key: string]: any; +} diff --git a/dist/lib/interfaces/token.interface.js b/dist/lib/interfaces/token.interface.js new file mode 100644 index 000000000..4391fbae5 --- /dev/null +++ b/dist/lib/interfaces/token.interface.js @@ -0,0 +1,3 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +//# sourceMappingURL=token.interface.js.map \ No newline at end of file diff --git a/dist/lib/interfaces/token.interface.js.map b/dist/lib/interfaces/token.interface.js.map new file mode 100644 index 000000000..dd925414f --- /dev/null +++ b/dist/lib/interfaces/token.interface.js.map @@ -0,0 +1 @@ +{"version":3,"file":"token.interface.js","sourceRoot":"","sources":["../../../lib/interfaces/token.interface.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/lib/interfaces/user.interface.d.ts b/dist/lib/interfaces/user.interface.d.ts new file mode 100644 index 000000000..61d349597 --- /dev/null +++ b/dist/lib/interfaces/user.interface.d.ts @@ -0,0 +1,3 @@ +export interface User { + [key: string]: any; +} diff --git a/dist/lib/interfaces/user.interface.js b/dist/lib/interfaces/user.interface.js new file mode 100644 index 000000000..583c676ab --- /dev/null +++ b/dist/lib/interfaces/user.interface.js @@ -0,0 +1,3 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +//# sourceMappingURL=user.interface.js.map \ No newline at end of file diff --git a/dist/lib/interfaces/user.interface.js.map b/dist/lib/interfaces/user.interface.js.map new file mode 100644 index 000000000..f94a53ef8 --- /dev/null +++ b/dist/lib/interfaces/user.interface.js.map @@ -0,0 +1 @@ +{"version":3,"file":"user.interface.js","sourceRoot":"","sources":["../../../lib/interfaces/user.interface.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/lib/models/index.d.ts b/dist/lib/models/index.d.ts new file mode 100644 index 000000000..34c69db1f --- /dev/null +++ b/dist/lib/models/index.d.ts @@ -0,0 +1 @@ +export { TokenModel } from './token-model'; diff --git a/dist/lib/models/index.js b/dist/lib/models/index.js new file mode 100644 index 000000000..3c49c3fd8 --- /dev/null +++ b/dist/lib/models/index.js @@ -0,0 +1,5 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var token_model_1 = require("./token-model"); +Object.defineProperty(exports, "TokenModel", { enumerable: true, get: function () { return token_model_1.TokenModel; } }); +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/lib/models/index.js.map b/dist/lib/models/index.js.map new file mode 100644 index 000000000..bea79d5a3 --- /dev/null +++ b/dist/lib/models/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../lib/models/index.ts"],"names":[],"mappings":";;AAAA,6CAA2C;AAAlC,yGAAA,UAAU,OAAA"} \ No newline at end of file diff --git a/dist/lib/models/token-model.d.ts b/dist/lib/models/token-model.d.ts new file mode 100644 index 000000000..e0a910963 --- /dev/null +++ b/dist/lib/models/token-model.d.ts @@ -0,0 +1,13 @@ +import { Client, Token, User } from '../interfaces'; +export declare class TokenModel implements Token { + accessToken: string; + accessTokenExpiresAt?: Date; + refreshToken?: string; + refreshTokenExpiresAt?: Date; + scope?: string; + client: Client; + user: User; + customAttributes: {}; + accessTokenLifetime: number; + constructor(data?: any, options?: any); +} diff --git a/dist/lib/models/token-model.js b/dist/lib/models/token-model.js new file mode 100644 index 000000000..f470e3b9a --- /dev/null +++ b/dist/lib/models/token-model.js @@ -0,0 +1,57 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TokenModel = void 0; +const constants_1 = require("../constants"); +const errors_1 = require("../errors"); +const fn_1 = require("../utils/fn"); +const modelAttributes = [ + 'accessToken', + 'accessTokenExpiresAt', + 'client', + 'refreshToken', + 'refreshTokenExpiresAt', + 'scope', + 'user', +]; +class TokenModel { + constructor(data = {}, options = {}) { + if (!data.accessToken) { + throw new errors_1.InvalidArgumentError('Missing parameter: `accessToken`'); + } + if (!data.client) { + throw new errors_1.InvalidArgumentError('Missing parameter: `client`'); + } + if (!data.user) { + throw new errors_1.InvalidArgumentError('Missing parameter: `user`'); + } + if (data.accessTokenExpiresAt && + !(data.accessTokenExpiresAt instanceof Date)) { + throw new errors_1.InvalidArgumentError('Invalid parameter: `accessTokenExpiresAt`'); + } + if (data.refreshTokenExpiresAt && + !(data.refreshTokenExpiresAt instanceof Date)) { + throw new errors_1.InvalidArgumentError('Invalid parameter: `refreshTokenExpiresAt`'); + } + this.accessToken = data.accessToken; + this.accessTokenExpiresAt = data.accessTokenExpiresAt; + this.client = data.client; + this.refreshToken = data.refreshToken; + this.refreshTokenExpiresAt = data.refreshTokenExpiresAt; + this.scope = data.scope; + this.user = data.user; + if (options && options.allowExtendedTokenAttributes) { + this.customAttributes = {}; + for (const key of Object.keys(data)) { + if (fn_1.hasOwnProperty(data, key) && modelAttributes.indexOf(key) < 0) { + this.customAttributes[key] = data[key]; + } + } + } + if (this.accessTokenExpiresAt) { + this.accessTokenLifetime = Math.floor((this.accessTokenExpiresAt.getTime() - new Date().getTime()) / + constants_1.MILLISECONDS_PER_SECOND); + } + } +} +exports.TokenModel = TokenModel; +//# sourceMappingURL=token-model.js.map \ No newline at end of file diff --git a/dist/lib/models/token-model.js.map b/dist/lib/models/token-model.js.map new file mode 100644 index 000000000..5a6fd51a9 --- /dev/null +++ b/dist/lib/models/token-model.js.map @@ -0,0 +1 @@ +{"version":3,"file":"token-model.js","sourceRoot":"","sources":["../../../lib/models/token-model.ts"],"names":[],"mappings":";;;AAAA,4CAAuD;AACvD,sCAAiD;AAEjD,oCAA6C;AAE7C,MAAM,eAAe,GAAG;IACtB,aAAa;IACb,sBAAsB;IACtB,QAAQ;IACR,cAAc;IACd,uBAAuB;IACvB,OAAO;IACP,MAAM;CACP,CAAC;AAEF,MAAa,UAAU;IAUrB,YAAY,OAAY,EAAE,EAAE,UAAe,EAAE;QAC3C,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;YACrB,MAAM,IAAI,6BAAoB,CAAC,kCAAkC,CAAC,CAAC;SACpE;QAED,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;YAChB,MAAM,IAAI,6BAAoB,CAAC,6BAA6B,CAAC,CAAC;SAC/D;QAED,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;YACd,MAAM,IAAI,6BAAoB,CAAC,2BAA2B,CAAC,CAAC;SAC7D;QAED,IACE,IAAI,CAAC,oBAAoB;YACzB,CAAC,CAAC,IAAI,CAAC,oBAAoB,YAAY,IAAI,CAAC,EAC5C;YACA,MAAM,IAAI,6BAAoB,CAC5B,2CAA2C,CAC5C,CAAC;SACH;QAED,IACE,IAAI,CAAC,qBAAqB;YAC1B,CAAC,CAAC,IAAI,CAAC,qBAAqB,YAAY,IAAI,CAAC,EAC7C;YACA,MAAM,IAAI,6BAAoB,CAC5B,4CAA4C,CAC7C,CAAC;SACH;QAED,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC;QACpC,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAC,oBAAoB,CAAC;QACtD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC;QACtC,IAAI,CAAC,qBAAqB,GAAG,IAAI,CAAC,qBAAqB,CAAC;QACxD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QAEtB,IAAI,OAAO,IAAI,OAAO,CAAC,4BAA4B,EAAE;YACnD,IAAI,CAAC,gBAAgB,GAAG,EAAE,CAAC;YAE3B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;gBACnC,IAAI,mBAAc,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,eAAe,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;oBACjE,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;iBACxC;aACF;SACF;QAED,IAAI,IAAI,CAAC,oBAAoB,EAAE;YAC7B,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC,KAAK,CACnC,CAAC,IAAI,CAAC,oBAAoB,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC;gBAC1D,mCAAuB,CAC1B,CAAC;SACH;IACH,CAAC;CACF;AAlED,gCAkEC"} \ No newline at end of file diff --git a/dist/lib/request.d.ts b/dist/lib/request.d.ts new file mode 100644 index 000000000..c6fdd3d5e --- /dev/null +++ b/dist/lib/request.d.ts @@ -0,0 +1,16 @@ +export declare class Request { + body: any; + headers: any; + method: string; + query: any; + constructor(options?: { + body: any; + headers: any; + method: string; + query: any; + [key: string]: any; + }); + get(field: string): any; + is(args: string[]): string | false; + is(...args: string[]): string | false; +} diff --git a/dist/lib/request.js b/dist/lib/request.js new file mode 100644 index 000000000..229c1d7ea --- /dev/null +++ b/dist/lib/request.js @@ -0,0 +1,48 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Request = void 0; +const typeis = require("type-is"); +const errors_1 = require("./errors"); +const fn_1 = require("./utils/fn"); +class Request { + constructor(options = {}) { + if (!options.headers) { + throw new errors_1.InvalidArgumentError('Missing parameter: `headers`'); + } + if (!options.method) { + throw new errors_1.InvalidArgumentError('Missing parameter: `method`'); + } + if (typeof options.method !== 'string') { + throw new errors_1.InvalidArgumentError('Invalid parameter: `method`'); + } + if (!options.query) { + throw new errors_1.InvalidArgumentError('Missing parameter: `query`'); + } + this.body = options.body || {}; + this.headers = {}; + this.method = options.method.toUpperCase(); + this.query = options.query; + for (const field of Object.keys(options.headers)) { + if (fn_1.hasOwnProperty(options.headers, field)) { + this.headers[field.toLowerCase()] = options.headers[field]; + } + } + for (const property of Object.keys(options)) { + if (fn_1.hasOwnProperty(options, property) && !this[property]) { + this[property] = options[property]; + } + } + } + get(field) { + return this.headers[field.toLowerCase()]; + } + is(...args) { + let types = args; + if (Array.isArray(types[0])) { + types = types[0]; + } + return typeis(this, types) || false; + } +} +exports.Request = Request; +//# sourceMappingURL=request.js.map \ No newline at end of file diff --git a/dist/lib/request.js.map b/dist/lib/request.js.map new file mode 100644 index 000000000..eafc0a012 --- /dev/null +++ b/dist/lib/request.js.map @@ -0,0 +1 @@ +{"version":3,"file":"request.js","sourceRoot":"","sources":["../../lib/request.ts"],"names":[],"mappings":";;;AAAA,kCAAkC;AAClC,qCAAgD;AAChD,mCAA4C;AAE5C,MAAa,OAAO;IAKlB,YACE,UAMI,EAAS;QAEb,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE;YACpB,MAAM,IAAI,6BAAoB,CAAC,8BAA8B,CAAC,CAAC;SAChE;QAED,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE;YACnB,MAAM,IAAI,6BAAoB,CAAC,6BAA6B,CAAC,CAAC;SAC/D;QAED,IAAI,OAAO,OAAO,CAAC,MAAM,KAAK,QAAQ,EAAE;YACtC,MAAM,IAAI,6BAAoB,CAAC,6BAA6B,CAAC,CAAC;SAC/D;QAED,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE;YAClB,MAAM,IAAI,6BAAoB,CAAC,4BAA4B,CAAC,CAAC;SAC9D;QAED,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC;QAC/B,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;QAClB,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QAC3C,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;QAG3B,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE;YAChD,IAAI,mBAAc,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE;gBAC1C,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;aAC5D;SACF;QAGD,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE;YAC3C,IAAI,mBAAc,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE;gBACxD,IAAI,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;aACpC;SACF;IACH,CAAC;IAMD,GAAG,CAAC,KAAa;QACf,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;IAC3C,CAAC;IAQD,EAAE,CAAC,GAAG,IAAI;QACR,IAAI,KAAK,GAAG,IAAI,CAAC;QACjB,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE;YAC3B,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;SAClB;QAED,OAAO,MAAM,CAAC,IAAW,EAAE,KAAK,CAAC,IAAI,KAAK,CAAC;IAC7C,CAAC;CACF;AAxED,0BAwEC"} \ No newline at end of file diff --git a/dist/lib/response-types/code-response-type.d.ts b/dist/lib/response-types/code-response-type.d.ts new file mode 100644 index 000000000..d354664db --- /dev/null +++ b/dist/lib/response-types/code-response-type.d.ts @@ -0,0 +1,15 @@ +import { AuthorizationCode, Client, Model, User } from '../interfaces'; +import { Request } from '../request'; +export declare class CodeResponseType { + code: any; + authorizationCodeLifetime: number; + model: Model; + constructor(options?: any); + handle(request: Request, client: Client, user: User, uri: string, scope: string): Promise; + getAuthorizationCodeExpiresAt(client: Client): Date; + getAuthorizationCodeLifetime(client: Client): any; + saveAuthorizationCode(authorizationCode: string, expiresAt: Date, scope: string, client: Client, redirectUri: any, user: User): Promise; + generateAuthorizationCode(client: Client, user: User, scope: string): Promise; + buildRedirectUri(redirectUri: any): any; + setRedirectUriParam(redirectUri: any, key: string, value: string): any; +} diff --git a/dist/lib/response-types/code-response-type.js b/dist/lib/response-types/code-response-type.js new file mode 100644 index 000000000..aa0fc3a72 --- /dev/null +++ b/dist/lib/response-types/code-response-type.js @@ -0,0 +1,83 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.CodeResponseType = void 0; +const constants_1 = require("../constants"); +const errors_1 = require("../errors"); +const tokenUtil = require("../utils/token-util"); +class CodeResponseType { + constructor(options = {}) { + if (!options.authorizationCodeLifetime) { + throw new errors_1.InvalidArgumentError('Missing parameter: `authorizationCodeLifetime`'); + } + if (!options.model) { + throw new errors_1.InvalidArgumentError('Missing parameter: `model`'); + } + if (!options.model.saveAuthorizationCode) { + throw new errors_1.InvalidArgumentError('Invalid argument: model does not implement `saveAuthorizationCode()`'); + } + this.code = undefined; + this.authorizationCodeLifetime = options.authorizationCodeLifetime; + this.model = options.model; + } + async handle(request, client, user, uri, scope) { + if (!request) { + throw new errors_1.InvalidArgumentError('Missing parameter: `request`'); + } + if (!client) { + throw new errors_1.InvalidArgumentError('Missing parameter: `client`'); + } + if (!user) { + throw new errors_1.InvalidArgumentError('Missing parameter: `user`'); + } + if (!uri) { + throw new errors_1.InvalidArgumentError('Missing parameter: `uri`'); + } + const authorizationCode = await this.generateAuthorizationCode(client, user, scope); + const expiresAt = this.getAuthorizationCodeExpiresAt(client); + const code = await this.saveAuthorizationCode(authorizationCode, expiresAt, scope, client, uri, user); + this.code = code.authorizationCode; + return code; + } + getAuthorizationCodeExpiresAt(client) { + const authorizationCodeLifetime = this.getAuthorizationCodeLifetime(client); + return new Date(Date.now() + authorizationCodeLifetime * constants_1.MILLISECONDS_PER_SECOND); + } + getAuthorizationCodeLifetime(client) { + return client.authorizationCodeLifetime || this.authorizationCodeLifetime; + } + async saveAuthorizationCode(authorizationCode, expiresAt, scope, client, redirectUri, user) { + const code = { + authorizationCode, + expiresAt, + redirectUri, + scope, + }; + return this.model.saveAuthorizationCode(code, client, user); + } + async generateAuthorizationCode(client, user, scope) { + if (this.model.generateAuthorizationCode) { + return this.model.generateAuthorizationCode(client, user, scope); + } + return tokenUtil.GenerateRandomToken(); + } + buildRedirectUri(redirectUri) { + if (!redirectUri) { + throw new errors_1.InvalidArgumentError('Missing parameter: `redirectUri`'); + } + redirectUri.search = undefined; + return this.setRedirectUriParam(redirectUri, 'code', this.code); + } + setRedirectUriParam(redirectUri, key, value) { + if (!redirectUri) { + throw new errors_1.InvalidArgumentError('Missing parameter: `redirectUri`'); + } + if (!key) { + throw new errors_1.InvalidArgumentError('Missing parameter: `key`'); + } + redirectUri.query = redirectUri.query || {}; + redirectUri.query[key] = value; + return redirectUri; + } +} +exports.CodeResponseType = CodeResponseType; +//# sourceMappingURL=code-response-type.js.map \ No newline at end of file diff --git a/dist/lib/response-types/code-response-type.js.map b/dist/lib/response-types/code-response-type.js.map new file mode 100644 index 000000000..6a24010d0 --- /dev/null +++ b/dist/lib/response-types/code-response-type.js.map @@ -0,0 +1 @@ +{"version":3,"file":"code-response-type.js","sourceRoot":"","sources":["../../../lib/response-types/code-response-type.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/lib/response-types/index.d.ts b/dist/lib/response-types/index.d.ts new file mode 100644 index 000000000..9866921eb --- /dev/null +++ b/dist/lib/response-types/index.d.ts @@ -0,0 +1,2 @@ +export { CodeResponseType } from './code-response-type'; +export { TokenResponseType } from './token-response-type'; diff --git a/dist/lib/response-types/index.js b/dist/lib/response-types/index.js new file mode 100644 index 000000000..a78bc31aa --- /dev/null +++ b/dist/lib/response-types/index.js @@ -0,0 +1,7 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var code_response_type_1 = require("./code-response-type"); +Object.defineProperty(exports, "CodeResponseType", { enumerable: true, get: function () { return code_response_type_1.CodeResponseType; } }); +var token_response_type_1 = require("./token-response-type"); +Object.defineProperty(exports, "TokenResponseType", { enumerable: true, get: function () { return token_response_type_1.TokenResponseType; } }); +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/lib/response-types/index.js.map b/dist/lib/response-types/index.js.map new file mode 100644 index 000000000..07151afe8 --- /dev/null +++ b/dist/lib/response-types/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../lib/response-types/index.ts"],"names":[],"mappings":";;AAAA,2DAAwD;AAA/C,sHAAA,gBAAgB,OAAA;AACzB,6DAA0D;AAAjD,wHAAA,iBAAiB,OAAA"} \ No newline at end of file diff --git a/dist/lib/response-types/token-response-type.d.ts b/dist/lib/response-types/token-response-type.d.ts new file mode 100644 index 000000000..bd6fed3e2 --- /dev/null +++ b/dist/lib/response-types/token-response-type.d.ts @@ -0,0 +1,12 @@ +import { Client, Model, User } from '../interfaces'; +import { Request } from '../request'; +export declare class TokenResponseType { + accessToken: string; + accessTokenLifetime: number; + model: Model; + constructor(options?: any); + handle(request: Request, client: Client, user: User, uri: string, scope: string): Promise; + getAccessTokenLifetime(client: Client): number; + buildRedirectUri(redirectUri: any): any; + setRedirectUriParam(redirectUri: any, key: string, value: any): any; +} diff --git a/dist/lib/response-types/token-response-type.js b/dist/lib/response-types/token-response-type.js new file mode 100644 index 000000000..1601f7c20 --- /dev/null +++ b/dist/lib/response-types/token-response-type.js @@ -0,0 +1,53 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TokenResponseType = void 0; +const errors_1 = require("../errors"); +const grant_types_1 = require("../grant-types"); +class TokenResponseType { + constructor(options = {}) { + if (!options.accessTokenLifetime) { + throw new errors_1.InvalidArgumentError('Missing parameter: `accessTokenLifetime`'); + } + this.accessToken = undefined; + this.accessTokenLifetime = options.accessTokenLifetime; + this.model = options.model; + } + async handle(request, client, user, uri, scope) { + if (!request) { + throw new errors_1.InvalidArgumentError('Missing parameter: `request`'); + } + if (!client) { + throw new errors_1.InvalidArgumentError('Missing parameter: `client`'); + } + const accessTokenLifetime = this.getAccessTokenLifetime(client); + const options = { + user, + scope, + model: this.model, + accessTokenLifetime, + }; + const grantType = new grant_types_1.ImplicitGrantType(options); + const token = await grantType.handle(request, client); + this.accessToken = token.accessToken; + return token; + } + getAccessTokenLifetime(client) { + return client.accessTokenLifetime || this.accessTokenLifetime; + } + buildRedirectUri(redirectUri) { + return this.setRedirectUriParam(redirectUri, 'access_token', this.accessToken); + } + setRedirectUriParam(redirectUri, key, value) { + if (!redirectUri) { + throw new errors_1.InvalidArgumentError('Missing parameter: `redirectUri`'); + } + if (!key) { + throw new errors_1.InvalidArgumentError('Missing parameter: `key`'); + } + redirectUri.hash = redirectUri.hash || ''; + redirectUri.hash += `${redirectUri.hash ? '&' : ''}${key}=${encodeURIComponent(value)}`; + return redirectUri; + } +} +exports.TokenResponseType = TokenResponseType; +//# sourceMappingURL=token-response-type.js.map \ No newline at end of file diff --git a/dist/lib/response-types/token-response-type.js.map b/dist/lib/response-types/token-response-type.js.map new file mode 100644 index 000000000..138f499ca --- /dev/null +++ b/dist/lib/response-types/token-response-type.js.map @@ -0,0 +1 @@ +{"version":3,"file":"token-response-type.js","sourceRoot":"","sources":["../../../lib/response-types/token-response-type.ts"],"names":[],"mappings":";;;AAAA,sCAAiD;AACjD,gDAAmD;AAInD,MAAa,iBAAiB;IAI5B,YAAY,UAAe,EAAE;QAC3B,IAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE;YAChC,MAAM,IAAI,6BAAoB,CAC5B,0CAA0C,CAC3C,CAAC;SACH;QAED,IAAI,CAAC,WAAW,GAAG,SAAS,CAAC;QAC7B,IAAI,CAAC,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC;QACvD,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;IAC7B,CAAC;IAMD,KAAK,CAAC,MAAM,CACV,OAAgB,EAChB,MAAc,EACd,IAAU,EACV,GAAW,EACX,KAAa;QAEb,IAAI,CAAC,OAAO,EAAE;YACZ,MAAM,IAAI,6BAAoB,CAAC,8BAA8B,CAAC,CAAC;SAChE;QAED,IAAI,CAAC,MAAM,EAAE;YACX,MAAM,IAAI,6BAAoB,CAAC,6BAA6B,CAAC,CAAC;SAC/D;QAED,MAAM,mBAAmB,GAAG,IAAI,CAAC,sBAAsB,CAAC,MAAM,CAAC,CAAC;QAEhE,MAAM,OAAO,GAAG;YACd,IAAI;YACJ,KAAK;YACL,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,mBAAmB;SACpB,CAAC;QAEF,MAAM,SAAS,GAAG,IAAI,+BAAiB,CAAC,OAAO,CAAC,CAAC;QACjD,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACtD,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC,WAAW,CAAC;QAErC,OAAO,KAAK,CAAC;IACf,CAAC;IAMD,sBAAsB,CAAC,MAAc;QACnC,OAAO,MAAM,CAAC,mBAAmB,IAAI,IAAI,CAAC,mBAAmB,CAAC;IAChE,CAAC;IAMD,gBAAgB,CAAC,WAAgB;QAC/B,OAAO,IAAI,CAAC,mBAAmB,CAC7B,WAAW,EACX,cAAc,EACd,IAAI,CAAC,WAAW,CACjB,CAAC;IACJ,CAAC;IAMD,mBAAmB,CAAC,WAAgB,EAAE,GAAW,EAAE,KAAU;QAC3D,IAAI,CAAC,WAAW,EAAE;YAChB,MAAM,IAAI,6BAAoB,CAAC,kCAAkC,CAAC,CAAC;SACpE;QAED,IAAI,CAAC,GAAG,EAAE;YACR,MAAM,IAAI,6BAAoB,CAAC,0BAA0B,CAAC,CAAC;SAC5D;QAED,WAAW,CAAC,IAAI,GAAG,WAAW,CAAC,IAAI,IAAI,EAAE,CAAC;QAC1C,WAAW,CAAC,IAAI,IAAI,GAClB,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAC3B,GAAG,GAAG,IAAI,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC;QAEtC,OAAO,WAAW,CAAC;IACrB,CAAC;CACF;AA3FD,8CA2FC"} \ No newline at end of file diff --git a/dist/lib/response.d.ts b/dist/lib/response.d.ts new file mode 100644 index 000000000..e1f705708 --- /dev/null +++ b/dist/lib/response.d.ts @@ -0,0 +1,9 @@ +export declare class Response { + body: any; + headers: any; + status: number; + constructor(options?: any); + get(field: string): any; + redirect(url: string): void; + set(field: string, value: string): void; +} diff --git a/dist/lib/response.js b/dist/lib/response.js new file mode 100644 index 000000000..b0b0313fd --- /dev/null +++ b/dist/lib/response.js @@ -0,0 +1,33 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Response = void 0; +const fn_1 = require("./utils/fn"); +class Response { + constructor(options = {}) { + this.body = options.body || {}; + this.headers = {}; + this.status = 200; + for (const field of Object.keys(options.headers || {})) { + if (fn_1.hasOwnProperty(options.headers, field)) { + this.headers[field.toLowerCase()] = options.headers[field]; + } + } + for (const property of Object.keys(options)) { + if (fn_1.hasOwnProperty(options, property) && !this[property]) { + this[property] = options[property]; + } + } + } + get(field) { + return this.headers[field.toLowerCase()]; + } + redirect(url) { + this.set('Location', url); + this.status = 302; + } + set(field, value) { + this.headers[field.toLowerCase()] = value; + } +} +exports.Response = Response; +//# sourceMappingURL=response.js.map \ No newline at end of file diff --git a/dist/lib/response.js.map b/dist/lib/response.js.map new file mode 100644 index 000000000..a07e0f312 --- /dev/null +++ b/dist/lib/response.js.map @@ -0,0 +1 @@ +{"version":3,"file":"response.js","sourceRoot":"","sources":["../../lib/response.ts"],"names":[],"mappings":";;;AAAA,mCAA4C;AAE5C,MAAa,QAAQ;IAInB,YAAY,UAAe,EAAE;QAC3B,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC;QAC/B,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;QAClB,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC;QAGlB,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE;YACtD,IAAI,mBAAc,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE;gBAC1C,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;aAC5D;SACF;QAGD,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE;YAC3C,IAAI,mBAAc,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE;gBACxD,IAAI,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;aACpC;SACF;IACH,CAAC;IAMD,GAAG,CAAC,KAAa;QACf,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;IAC3C,CAAC;IAMD,QAAQ,CAAC,GAAW;QAClB,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;QAC1B,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC;IACpB,CAAC;IAMD,GAAG,CAAC,KAAa,EAAE,KAAa;QAC9B,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,GAAG,KAAK,CAAC;IAC5C,CAAC;CACF;AAhDD,4BAgDC"} \ No newline at end of file diff --git a/dist/lib/server.d.ts b/dist/lib/server.d.ts new file mode 100644 index 000000000..0e57b089d --- /dev/null +++ b/dist/lib/server.d.ts @@ -0,0 +1,11 @@ +import { Request } from './request'; +import { Response } from './response'; +export declare class OAuth2Server { + options: any; + constructor(options?: any); + authenticate(request: Request, response?: Response, scope?: string): Promise; + authenticate(request: Request, response?: Response, options?: any): Promise; + authorize(request: Request, response: Response, options?: any): Promise; + token(request: Request, response: Response, options?: any): Promise; + revoke(request: Request, response: Response, options: any): Promise; +} diff --git a/dist/lib/server.js b/dist/lib/server.js new file mode 100644 index 000000000..9e94cf145 --- /dev/null +++ b/dist/lib/server.js @@ -0,0 +1,36 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.OAuth2Server = void 0; +const constants_1 = require("./constants"); +const errors_1 = require("./errors"); +const handlers_1 = require("./handlers"); +class OAuth2Server { + constructor(options = {}) { + if (!options.model) { + throw new errors_1.InvalidArgumentError('Missing parameter: `model`'); + } + this.options = options; + } + async authenticate(request, response, options) { + let opt = options; + if (typeof opt === 'string') { + opt = { scope: opt }; + } + opt = Object.assign(Object.assign({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: true, allowBearerTokensInQueryString: false }, this.options), opt); + return new handlers_1.AuthenticateHandler(opt).handle(request, response); + } + async authorize(request, response, options) { + const opts = Object.assign(Object.assign({ allowEmptyState: false, accessTokenLifetime: constants_1.HOUR / constants_1.SECOND, authorizationCodeLifetime: (constants_1.MINUTE * 5) / constants_1.SECOND }, this.options), options); + return new handlers_1.AuthorizeHandler(opts).handle(request, response); + } + async token(request, response, options) { + const opts = Object.assign(Object.assign({ accessTokenLifetime: constants_1.HOUR / constants_1.SECOND, refreshTokenLifetime: (constants_1.WEEK * 2) / constants_1.SECOND, allowExtendedTokenAttributes: false, requireClientAuthentication: {} }, this.options), options); + return new handlers_1.TokenHandler(opts).handle(request, response); + } + async revoke(request, response, options) { + const opt = Object.assign(Object.assign({}, this.options), options); + return new handlers_1.RevokeHandler(opt).handle(request, response); + } +} +exports.OAuth2Server = OAuth2Server; +//# sourceMappingURL=server.js.map \ No newline at end of file diff --git a/dist/lib/server.js.map b/dist/lib/server.js.map new file mode 100644 index 000000000..74b625faf --- /dev/null +++ b/dist/lib/server.js.map @@ -0,0 +1 @@ +{"version":3,"file":"server.js","sourceRoot":"","sources":["../../lib/server.ts"],"names":[],"mappings":";;;AAAA,2CAAyD;AACzD,qCAAgD;AAChD,yCAKoB;AAIpB,MAAa,YAAY;IAEvB,YAAY,UAAe,EAAE;QAC3B,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE;YAClB,MAAM,IAAI,6BAAoB,CAAC,4BAA4B,CAAC,CAAC;SAC9D;QAED,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAiBD,KAAK,CAAC,YAAY,CAChB,OAAgB,EAChB,QAAmB,EACnB,OAAsB;QAEtB,IAAI,GAAG,GAAG,OAAO,CAAC;QAClB,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE;YAC3B,GAAG,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;SACtB;QAED,GAAG,iCACD,uBAAuB,EAAE,IAAI,EAC7B,yBAAyB,EAAE,IAAI,EAC/B,8BAA8B,EAAE,KAAK,IAClC,IAAI,CAAC,OAAO,GACZ,GAAG,CACP,CAAC;QAEF,OAAO,IAAI,8BAAmB,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAChE,CAAC;IAMD,KAAK,CAAC,SAAS,CAAC,OAAgB,EAAE,QAAkB,EAAE,OAAa;QACjE,MAAM,IAAI,iCACR,eAAe,EAAE,KAAK,EACtB,mBAAmB,EAAE,gBAAI,GAAG,kBAAM,EAClC,yBAAyB,EAAE,CAAC,kBAAM,GAAG,CAAC,CAAC,GAAG,kBAAM,IAC7C,IAAI,CAAC,OAAO,GACZ,OAAO,CACX,CAAC;QAEF,OAAO,IAAI,2BAAgB,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC9D,CAAC;IAMD,KAAK,CAAC,KAAK,CAAC,OAAgB,EAAE,QAAkB,EAAE,OAAa;QAC7D,MAAM,IAAI,iCACR,mBAAmB,EAAE,gBAAI,GAAG,kBAAM,EAClC,oBAAoB,EAAE,CAAC,gBAAI,GAAG,CAAC,CAAC,GAAG,kBAAM,EACzC,4BAA4B,EAAE,KAAK,EACnC,2BAA2B,EAAE,EAAE,IAC5B,IAAI,CAAC,OAAO,GACZ,OAAO,CACX,CAAC;QAEF,OAAO,IAAI,uBAAY,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC1D,CAAC;IAMD,KAAK,CAAC,MAAM,CAAC,OAAgB,EAAE,QAAkB,EAAE,OAAY;QAC7D,MAAM,GAAG,mCAAQ,IAAI,CAAC,OAAO,GAAK,OAAO,CAAE,CAAC;QAE5C,OAAO,IAAI,wBAAa,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC1D,CAAC;CACF;AAxFD,oCAwFC"} \ No newline at end of file diff --git a/dist/lib/token-types/bearer-token-type.d.ts b/dist/lib/token-types/bearer-token-type.d.ts new file mode 100644 index 000000000..e2edc4ff8 --- /dev/null +++ b/dist/lib/token-types/bearer-token-type.d.ts @@ -0,0 +1,9 @@ +export declare class BearerTokenType { + accessToken: string; + accessTokenLifetime: number; + refreshToken: string; + scope: string; + customAttributes: any; + constructor(accessToken: string, accessTokenLifetime: number, refreshToken: string, scope: string, customAttributes: any); + valueOf(): any; +} diff --git a/dist/lib/token-types/bearer-token-type.js b/dist/lib/token-types/bearer-token-type.js new file mode 100644 index 000000000..dcb0da11c --- /dev/null +++ b/dist/lib/token-types/bearer-token-type.js @@ -0,0 +1,42 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.BearerTokenType = void 0; +const errors_1 = require("../errors"); +const fn_1 = require("../utils/fn"); +class BearerTokenType { + constructor(accessToken, accessTokenLifetime, refreshToken, scope, customAttributes) { + if (!accessToken) { + throw new errors_1.InvalidArgumentError('Missing parameter: `accessToken`'); + } + this.accessToken = accessToken; + this.accessTokenLifetime = accessTokenLifetime; + this.refreshToken = refreshToken; + this.scope = scope; + if (customAttributes) { + this.customAttributes = customAttributes; + } + } + valueOf() { + const object = { + access_token: this.accessToken, + token_type: 'Bearer', + }; + if (this.accessTokenLifetime) { + object.expires_in = this.accessTokenLifetime; + } + if (this.refreshToken) { + object.refresh_token = this.refreshToken; + } + if (this.scope) { + object.scope = this.scope; + } + for (const key of Object.keys(this.customAttributes || {})) { + if (fn_1.hasOwnProperty(this.customAttributes, key)) { + object[key] = this.customAttributes[key]; + } + } + return object; + } +} +exports.BearerTokenType = BearerTokenType; +//# sourceMappingURL=bearer-token-type.js.map \ No newline at end of file diff --git a/dist/lib/token-types/bearer-token-type.js.map b/dist/lib/token-types/bearer-token-type.js.map new file mode 100644 index 000000000..25d3d18c2 --- /dev/null +++ b/dist/lib/token-types/bearer-token-type.js.map @@ -0,0 +1 @@ +{"version":3,"file":"bearer-token-type.js","sourceRoot":"","sources":["../../../lib/token-types/bearer-token-type.ts"],"names":[],"mappings":";;;AAAA,sCAAiD;AACjD,oCAA6C;AAE7C,MAAa,eAAe;IAM1B,YACE,WAAmB,EACnB,mBAA2B,EAC3B,YAAoB,EACpB,KAAa,EACb,gBAAqB;QAErB,IAAI,CAAC,WAAW,EAAE;YAChB,MAAM,IAAI,6BAAoB,CAAC,kCAAkC,CAAC,CAAC;SACpE;QAED,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,mBAAmB,GAAG,mBAAmB,CAAC;QAC/C,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QAEnB,IAAI,gBAAgB,EAAE;YACpB,IAAI,CAAC,gBAAgB,GAAG,gBAAgB,CAAC;SAC1C;IACH,CAAC;IAMD,OAAO;QACL,MAAM,MAAM,GAAQ;YAClB,YAAY,EAAE,IAAI,CAAC,WAAW;YAC9B,UAAU,EAAE,QAAQ;SACrB,CAAC;QAEF,IAAI,IAAI,CAAC,mBAAmB,EAAE;YAC5B,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC,mBAAmB,CAAC;SAC9C;QAED,IAAI,IAAI,CAAC,YAAY,EAAE;YACrB,MAAM,CAAC,aAAa,GAAG,IAAI,CAAC,YAAY,CAAC;SAC1C;QAED,IAAI,IAAI,CAAC,KAAK,EAAE;YACd,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;SAC3B;QAED,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,IAAI,EAAE,CAAC,EAAE;YAC1D,IAAI,mBAAc,CAAC,IAAI,CAAC,gBAAgB,EAAE,GAAG,CAAC,EAAE;gBAC9C,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC;aAC1C;SACF;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;CACF;AAzDD,0CAyDC"} \ No newline at end of file diff --git a/dist/lib/token-types/index.d.ts b/dist/lib/token-types/index.d.ts new file mode 100644 index 000000000..7fa71bce4 --- /dev/null +++ b/dist/lib/token-types/index.d.ts @@ -0,0 +1,2 @@ +export { BearerTokenType } from './bearer-token-type'; +export { MacTokenType } from './mac-token-type'; diff --git a/dist/lib/token-types/index.js b/dist/lib/token-types/index.js new file mode 100644 index 000000000..f87110945 --- /dev/null +++ b/dist/lib/token-types/index.js @@ -0,0 +1,7 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var bearer_token_type_1 = require("./bearer-token-type"); +Object.defineProperty(exports, "BearerTokenType", { enumerable: true, get: function () { return bearer_token_type_1.BearerTokenType; } }); +var mac_token_type_1 = require("./mac-token-type"); +Object.defineProperty(exports, "MacTokenType", { enumerable: true, get: function () { return mac_token_type_1.MacTokenType; } }); +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/lib/token-types/index.js.map b/dist/lib/token-types/index.js.map new file mode 100644 index 000000000..7be9bfd48 --- /dev/null +++ b/dist/lib/token-types/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../lib/token-types/index.ts"],"names":[],"mappings":";;AAAA,yDAAsD;AAA7C,oHAAA,eAAe,OAAA;AACxB,mDAAgD;AAAvC,8GAAA,YAAY,OAAA"} \ No newline at end of file diff --git a/dist/lib/token-types/mac-token-type.d.ts b/dist/lib/token-types/mac-token-type.d.ts new file mode 100644 index 000000000..8114695bc --- /dev/null +++ b/dist/lib/token-types/mac-token-type.d.ts @@ -0,0 +1,3 @@ +export declare class MacTokenType { + constructor(); +} diff --git a/dist/lib/token-types/mac-token-type.js b/dist/lib/token-types/mac-token-type.js new file mode 100644 index 000000000..ca1cb944a --- /dev/null +++ b/dist/lib/token-types/mac-token-type.js @@ -0,0 +1,11 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.MacTokenType = void 0; +const errors_1 = require("../errors"); +class MacTokenType { + constructor() { + throw new errors_1.ServerError('Not implemented.'); + } +} +exports.MacTokenType = MacTokenType; +//# sourceMappingURL=mac-token-type.js.map \ No newline at end of file diff --git a/dist/lib/token-types/mac-token-type.js.map b/dist/lib/token-types/mac-token-type.js.map new file mode 100644 index 000000000..ec11ce262 --- /dev/null +++ b/dist/lib/token-types/mac-token-type.js.map @@ -0,0 +1 @@ +{"version":3,"file":"mac-token-type.js","sourceRoot":"","sources":["../../../lib/token-types/mac-token-type.ts"],"names":[],"mappings":";;;AAAA,sCAAwC;AAGxC,MAAa,YAAY;IACvB;QACE,MAAM,IAAI,oBAAW,CAAC,kBAAkB,CAAC,CAAC;IAC5C,CAAC;CACF;AAJD,oCAIC"} \ No newline at end of file diff --git a/dist/lib/utils/fn.d.ts b/dist/lib/utils/fn.d.ts new file mode 100644 index 000000000..1e7a0f469 --- /dev/null +++ b/dist/lib/utils/fn.d.ts @@ -0,0 +1,6 @@ +export declare const oneSuccess: (promises: Array>) => Promise; +export declare const hasOwnProperty: (o: any, k: string) => any; +export declare class AggregateError extends Array implements Error { + name: string; + get message(): string; +} diff --git a/dist/lib/utils/fn.js b/dist/lib/utils/fn.js new file mode 100644 index 000000000..14e08ce0a --- /dev/null +++ b/dist/lib/utils/fn.js @@ -0,0 +1,18 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AggregateError = exports.hasOwnProperty = exports.oneSuccess = void 0; +const identity = (v) => v; +const reverser = (promise) => promise.then(v => Promise.reject(v), identity); +exports.oneSuccess = (promises) => Promise.all(promises.map(reverser)).then(e => Promise.reject(AggregateError.from(e)), identity); +exports.hasOwnProperty = (o, k) => Object.prototype.hasOwnProperty.call(o, k); +class AggregateError extends Array { + constructor() { + super(...arguments); + this.name = 'AggregateError'; + } + get message() { + return this.map(e => e.message).join('\n'); + } +} +exports.AggregateError = AggregateError; +//# sourceMappingURL=fn.js.map \ No newline at end of file diff --git a/dist/lib/utils/fn.js.map b/dist/lib/utils/fn.js.map new file mode 100644 index 000000000..2c7b4845c --- /dev/null +++ b/dist/lib/utils/fn.js.map @@ -0,0 +1 @@ +{"version":3,"file":"fn.js","sourceRoot":"","sources":["../../../lib/utils/fn.ts"],"names":[],"mappings":";;;AAAA,MAAM,QAAQ,GAAG,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC;AAE/B,MAAM,QAAQ,GAAG,CAAC,OAAqB,EAAE,EAAE,CACzC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;AAEpC,QAAA,UAAU,GAAG,CAAC,QAA6B,EAAE,EAAE,CAC1D,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CACtC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAC3C,QAAQ,CACT,CAAC;AAES,QAAA,cAAc,GAAG,CAAC,CAAM,EAAE,CAAS,EAAE,EAAE,CAClD,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAE7C,MAAa,cAAe,SAAQ,KAAK;IAAzC;;QACE,SAAI,GAAG,gBAAgB,CAAC;IAI1B,CAAC;IAHC,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7C,CAAC;CACF;AALD,wCAKC"} \ No newline at end of file diff --git a/dist/lib/utils/token-util.d.ts b/dist/lib/utils/token-util.d.ts new file mode 100644 index 000000000..776d65d05 --- /dev/null +++ b/dist/lib/utils/token-util.d.ts @@ -0,0 +1 @@ +export declare const GenerateRandomToken: () => Promise; diff --git a/dist/lib/utils/token-util.js b/dist/lib/utils/token-util.js new file mode 100644 index 000000000..6c5e3d249 --- /dev/null +++ b/dist/lib/utils/token-util.js @@ -0,0 +1,14 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.GenerateRandomToken = void 0; +const crypto_1 = require("crypto"); +const util_1 = require("util"); +const randomBytesPromise = util_1.promisify(crypto_1.randomBytes); +exports.GenerateRandomToken = async () => { + const bytesSize = 256; + const buffer = await randomBytesPromise(bytesSize); + return crypto_1.createHash('sha1') + .update(buffer) + .digest('hex'); +}; +//# sourceMappingURL=token-util.js.map \ No newline at end of file diff --git a/dist/lib/utils/token-util.js.map b/dist/lib/utils/token-util.js.map new file mode 100644 index 000000000..2369ed62c --- /dev/null +++ b/dist/lib/utils/token-util.js.map @@ -0,0 +1 @@ +{"version":3,"file":"token-util.js","sourceRoot":"","sources":["../../../lib/utils/token-util.ts"],"names":[],"mappings":";;;AAAA,mCAAiD;AACjD,+BAAiC;AACjC,MAAM,kBAAkB,GAAG,gBAAS,CAAC,oBAAW,CAAC,CAAC;AAMrC,QAAA,mBAAmB,GAAG,KAAK,IAAI,EAAE;IAC5C,MAAM,SAAS,GAAG,GAAG,CAAC;IACtB,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC,SAAS,CAAC,CAAC;IAEnD,OAAO,mBAAU,CAAC,MAAM,CAAC;SACtB,MAAM,CAAC,MAAM,CAAC;SACd,MAAM,CAAC,KAAK,CAAC,CAAC;AACnB,CAAC,CAAC"} \ No newline at end of file diff --git a/dist/lib/validator/is.d.ts b/dist/lib/validator/is.d.ts new file mode 100644 index 000000000..db83e40c2 --- /dev/null +++ b/dist/lib/validator/is.d.ts @@ -0,0 +1,6 @@ +export declare const nchar: (value: string) => boolean; +export declare const nqchar: (value: string) => boolean; +export declare const nqschar: (value: string) => boolean; +export declare const uchar: (value: string) => boolean; +export declare const uri: (value: string) => boolean; +export declare const vschar: (value: string) => boolean; diff --git a/dist/lib/validator/is.js b/dist/lib/validator/is.js new file mode 100644 index 000000000..09a8480f3 --- /dev/null +++ b/dist/lib/validator/is.js @@ -0,0 +1,18 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.vschar = exports.uri = exports.uchar = exports.nqschar = exports.nqchar = exports.nchar = void 0; +const Rules = { + NCHAR: /^[\u002D|\u002E|\u005F|\w]+$/, + NQCHAR: /^[\u0021|\u0023-\u005B|\u005D-\u007E]+$/, + NQSCHAR: /^[\u0020-\u0021|\u0023-\u005B|\u005D-\u007E]+$/, + UNICODECHARNOCRLF: /^[\u0009|\u0020-\u007E|\u0080-\uD7FF|\uE000-\uFFFD|\u10000-\u10FFFF]+$/, + URI: /^[a-zA-Z][a-zA-Z0-9+.-]+:/, + VSCHAR: /^[\u0020-\u007E]+$/, +}; +exports.nchar = (value) => Rules.NCHAR.test(value); +exports.nqchar = (value) => Rules.NQCHAR.test(value); +exports.nqschar = (value) => Rules.NQSCHAR.test(value); +exports.uchar = (value) => Rules.UNICODECHARNOCRLF.test(value); +exports.uri = (value) => Rules.URI.test(value); +exports.vschar = (value) => Rules.VSCHAR.test(value); +//# sourceMappingURL=is.js.map \ No newline at end of file diff --git a/dist/lib/validator/is.js.map b/dist/lib/validator/is.js.map new file mode 100644 index 000000000..13cd7df0b --- /dev/null +++ b/dist/lib/validator/is.js.map @@ -0,0 +1 @@ +{"version":3,"file":"is.js","sourceRoot":"","sources":["../../../lib/validator/is.ts"],"names":[],"mappings":";;;AAIA,MAAM,KAAK,GAAG;IACZ,KAAK,EAAE,8BAA8B;IACrC,MAAM,EAAE,yCAAyC;IACjD,OAAO,EAAE,gDAAgD;IACzD,iBAAiB,EAAE,wEAAwE;IAC3F,GAAG,EAAE,2BAA2B;IAChC,MAAM,EAAE,oBAAoB;CAC7B,CAAC;AAYW,QAAA,KAAK,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAQnD,QAAA,MAAM,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAQrD,QAAA,OAAO,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AASvD,QAAA,KAAK,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,KAAK,CAAC,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAO/D,QAAA,GAAG,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAQ/C,QAAA,MAAM,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC"} \ No newline at end of file diff --git a/dist/package.json b/dist/package.json new file mode 100644 index 000000000..91843f986 --- /dev/null +++ b/dist/package.json @@ -0,0 +1,31 @@ +{ + "name": "oauth2-server-ts", + "description": "Complete, framework-agnostic, compliant and well tested module for implementing an OAuth2 Server in node.js", + "version": "6.0.0+alpha", + "keywords": [ + "oauth", + "oauth2" + ], + "main": "index.js", + "engines": { + "node": ">=8.10" + }, + "license": "MIT", + "repository": { + "url": "https://github.com/oauthjs/node-oauth2-server.git", + "type": "git" + }, + "dependencies": { + "@types/basic-auth": "^1.1.2", + "@types/node": "^11.15.3", + "@types/statuses": "^1.5.0", + "@types/type-is": "^1.6.3", + "basic-auth": "^2.0.1", + "bluebird": "^3.7.2", + "lodash": "^4.17.20", + "promisify-any": "^2.0.1", + "statuses": "^2.0.0", + "tslib": "^1.10.0", + "type-is": "^1.6.18" + } +} \ No newline at end of file diff --git a/dist/test/integration/grant-types/abstract-grant-type.spec.d.ts b/dist/test/integration/grant-types/abstract-grant-type.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/dist/test/integration/grant-types/abstract-grant-type.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/test/integration/grant-types/abstract-grant-type.spec.js b/dist/test/integration/grant-types/abstract-grant-type.spec.js new file mode 100644 index 000000000..1db04fa43 --- /dev/null +++ b/dist/test/integration/grant-types/abstract-grant-type.spec.js @@ -0,0 +1,209 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const should = require("should"); +const errors_1 = require("../../../lib/errors"); +const grant_types_1 = require("../../../lib/grant-types"); +const request_1 = require("../../../lib/request"); +describe('AbstractGrantType integration', () => { + describe('constructor()', () => { + it('should throw an error if `options.accessTokenLifetime` is missing', () => { + try { + new grant_types_1.AbstractGrantType(); + should.fail('no error was thrown', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `accessTokenLifetime`'); + } + }); + it('should throw an error if `options.model` is missing', () => { + try { + new grant_types_1.AbstractGrantType({ accessTokenLifetime: 123 }); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `model`'); + } + }); + it('should set the `accessTokenLifetime`', () => { + const grantType = new grant_types_1.AbstractGrantType({ + accessTokenLifetime: 123, + model: {}, + }); + grantType.accessTokenLifetime.should.equal(123); + }); + it('should set the `model`', () => { + const model = {}; + const grantType = new grant_types_1.AbstractGrantType({ + accessTokenLifetime: 123, + model, + }); + grantType.model.should.equal(model); + }); + it('should set the `refreshTokenLifetime`', () => { + const grantType = new grant_types_1.AbstractGrantType({ + accessTokenLifetime: 123, + model: {}, + refreshTokenLifetime: 456, + }); + grantType.refreshTokenLifetime.should.equal(456); + }); + }); + describe('generateAccessToken()', () => { + it('should return an access token', async () => { + const handler = new grant_types_1.AbstractGrantType({ + accessTokenLifetime: 123, + model: {}, + refreshTokenLifetime: 456, + }); + try { + const data = await handler.generateAccessToken(); + data.should.be.a.sha1(); + } + catch (error) { + should.fail('should.fail', ''); + } + }); + it('should support promises', () => { + const model = { + generateAccessToken() { + return Promise.resolve({}); + }, + }; + const handler = new grant_types_1.AbstractGrantType({ + accessTokenLifetime: 123, + model, + refreshTokenLifetime: 456, + }); + handler.generateAccessToken().should.be.an.instanceOf(Promise); + }); + it('should support non-promises', () => { + const model = { + generateAccessToken() { + return {}; + }, + }; + const handler = new grant_types_1.AbstractGrantType({ + accessTokenLifetime: 123, + model, + refreshTokenLifetime: 456, + }); + handler.generateAccessToken().should.be.an.instanceOf(Promise); + }); + }); + describe('generateRefreshToken()', () => { + it('should return a refresh token', async () => { + const handler = new grant_types_1.AbstractGrantType({ + accessTokenLifetime: 123, + model: {}, + refreshTokenLifetime: 456, + }); + try { + const data = await handler.generateRefreshToken(); + data.should.be.a.sha1(); + } + catch (error) { + should.fail('should.fail fail', error.message); + } + }); + it('should support promises', () => { + const model = { + generateRefreshToken() { + return Promise.resolve({}); + }, + }; + const handler = new grant_types_1.AbstractGrantType({ + accessTokenLifetime: 123, + model, + refreshTokenLifetime: 456, + }); + handler.generateRefreshToken().should.be.an.instanceOf(Promise); + }); + it('should support non-promises', () => { + const model = { + generateRefreshToken() { + return {}; + }, + }; + const handler = new grant_types_1.AbstractGrantType({ + accessTokenLifetime: 123, + model, + refreshTokenLifetime: 456, + }); + handler.generateRefreshToken().should.be.an.instanceOf(Promise); + }); + }); + describe('getAccessTokenExpiresAt()', () => { + it('should return a date', () => { + const handler = new grant_types_1.AbstractGrantType({ + accessTokenLifetime: 123, + model: {}, + refreshTokenLifetime: 456, + }); + handler.getAccessTokenExpiresAt().should.be.an.instanceOf(Date); + }); + }); + describe('getRefreshTokenExpiresAt()', () => { + it('should return a refresh token', () => { + const handler = new grant_types_1.AbstractGrantType({ + accessTokenLifetime: 123, + model: {}, + refreshTokenLifetime: 456, + }); + handler.getRefreshTokenExpiresAt().should.be.an.instanceOf(Date); + }); + }); + describe('getScope()', () => { + it('should throw an error if `scope` is invalid', () => { + const handler = new grant_types_1.AbstractGrantType({ + accessTokenLifetime: 123, + model: {}, + refreshTokenLifetime: 456, + }); + const request = new request_1.Request({ + body: { scope: 'øå€£‰' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + handler.getScope(request); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Invalid parameter: `scope`'); + } + }); + it('should allow the `scope` to be `undefined`', () => { + const handler = new grant_types_1.AbstractGrantType({ + accessTokenLifetime: 123, + model: {}, + refreshTokenLifetime: 456, + }); + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + should.not.exist(handler.getScope(request)); + }); + it('should return the scope', () => { + const handler = new grant_types_1.AbstractGrantType({ + accessTokenLifetime: 123, + model: {}, + refreshTokenLifetime: 456, + }); + const request = new request_1.Request({ + body: { scope: 'foo' }, + headers: {}, + method: 'ANY', + query: {}, + }); + handler.getScope(request).should.equal('foo'); + }); + }); +}); +//# sourceMappingURL=abstract-grant-type.spec.js.map \ No newline at end of file diff --git a/dist/test/integration/grant-types/abstract-grant-type.spec.js.map b/dist/test/integration/grant-types/abstract-grant-type.spec.js.map new file mode 100644 index 000000000..7ebcd368c --- /dev/null +++ b/dist/test/integration/grant-types/abstract-grant-type.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"abstract-grant-type.spec.js","sourceRoot":"","sources":["../../../../test/integration/grant-types/abstract-grant-type.spec.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/test/integration/grant-types/authorization-code-grant-type.spec.d.ts b/dist/test/integration/grant-types/authorization-code-grant-type.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/dist/test/integration/grant-types/authorization-code-grant-type.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/test/integration/grant-types/authorization-code-grant-type.spec.js b/dist/test/integration/grant-types/authorization-code-grant-type.spec.js new file mode 100644 index 000000000..75045cc3a --- /dev/null +++ b/dist/test/integration/grant-types/authorization-code-grant-type.spec.js @@ -0,0 +1,840 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const should = require("should"); +const errors_1 = require("../../../lib/errors"); +const grant_types_1 = require("../../../lib/grant-types"); +const request_1 = require("../../../lib/request"); +describe('AuthorizationCodeGrantType integration', () => { + describe('constructor()', () => { + it('should throw an error if `model` is missing', () => { + try { + new grant_types_1.AuthorizationCodeGrantType({ accessTokenLifetime: 3600 }); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `model`'); + } + }); + it('should throw an error if the model does not implement `getAuthorizationCode()`', () => { + try { + new grant_types_1.AuthorizationCodeGrantType({ + accessTokenLifetime: 3600, + model: {}, + }); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Invalid argument: model does not implement `getAuthorizationCode()`'); + } + }); + it('should throw an error if the model does not implement `revokeAuthorizationCode()`', () => { + try { + const model = { + getAuthorizationCode: () => { }, + }; + new grant_types_1.AuthorizationCodeGrantType({ accessTokenLifetime: 3600, model }); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Invalid argument: model does not implement `revokeAuthorizationCode()`'); + } + }); + it('should throw an error if the model does not implement `saveToken()`', () => { + try { + const model = { + getAuthorizationCode: () => { }, + revokeAuthorizationCode: () => { }, + }; + new grant_types_1.AuthorizationCodeGrantType({ accessTokenLifetime: 3600, model }); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Invalid argument: model does not implement `saveToken()`'); + } + }); + }); + describe('handle()', () => { + it('should throw an error if `request` is missing', async () => { + const model = { + getAuthorizationCode: () => { }, + revokeAuthorizationCode: () => { }, + saveToken: () => { }, + }; + const grantType = new grant_types_1.AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + try { + await grantType.handle(undefined, undefined); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `request`'); + } + }); + it('should throw an error if `client` is invalid', () => { + const client = {}; + const model = { + getAuthorizationCode() { + return { + authorizationCode: 12345, + expiresAt: new Date(new Date().getTime() * 2), + user: {}, + }; + }, + revokeAuthorizationCode() { }, + saveToken() { }, + }; + const grantType = new grant_types_1.AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + return grantType + .handle(request, client) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.ServerError); + e.message.should.equal('Server error: `getAuthorizationCode()` did not return a `client` object'); + }); + }); + it('should throw an error if `client` is missing', async () => { + const model = { + getAuthorizationCode: () => { + return { + authorizationCode: 12345, + expiresAt: new Date(new Date().getTime() * 2), + user: {}, + }; + }, + revokeAuthorizationCode: () => { }, + saveToken: () => { }, + }; + const grantType = new grant_types_1.AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await grantType.handle(request, undefined); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `client`'); + } + }); + it('should return a token', async () => { + const client = { id: 'foobar' }; + const token = {}; + const model = { + getAuthorizationCode: () => { + return { + authorizationCode: 12345, + client: { id: 'foobar' }, + expiresAt: new Date(new Date().getTime() * 2), + user: {}, + }; + }, + revokeAuthorizationCode: () => { + return true; + }, + saveToken: () => { + return token; + }, + validateScope: () => { + return 'foo'; + }, + }; + const grantType = new grant_types_1.AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + const data = await grantType.handle(request, client); + data.should.equal(token); + } + catch (e) { + should.fail('should.fail', ''); + } + }); + it('should support promises', () => { + const client = { id: 'foobar' }; + const model = { + getAuthorizationCode: () => { + return Promise.resolve({ + authorizationCode: 12345, + client: { id: 'foobar' }, + expiresAt: new Date(new Date().getTime() * 2), + user: {}, + }); + }, + revokeAuthorizationCode: () => { + return true; + }, + saveToken: () => { }, + }; + const grantType = new grant_types_1.AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + grantType.handle(request, client).should.be.an.instanceOf(Promise); + }); + it('should support non-promises', () => { + const client = { id: 'foobar' }; + const model = { + getAuthorizationCode: () => { + return { + authorizationCode: 12345, + client: { id: 'foobar' }, + expiresAt: new Date(new Date().getTime() * 2), + user: {}, + }; + }, + revokeAuthorizationCode: () => { + return true; + }, + saveToken: () => { }, + }; + const grantType = new grant_types_1.AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + grantType.handle(request, client).should.be.an.instanceOf(Promise); + }); + }); + describe('getAuthorizationCode()', () => { + it('should throw an error if the request body does not contain `code`', async () => { + const client = {}; + const model = { + getAuthorizationCode: () => { }, + revokeAuthorizationCode: () => { }, + saveToken: () => { }, + }; + const grantType = new grant_types_1.AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await grantType.getAuthorizationCode(request, client); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidRequestError); + e.message.should.equal('Missing parameter: `code`'); + } + }); + it('should throw an error if `code` is invalid', async () => { + const client = {}; + const model = { + getAuthorizationCode: () => { }, + revokeAuthorizationCode: () => { }, + saveToken: () => { }, + }; + const grantType = new grant_types_1.AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: { code: 'øå€£‰' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await grantType.getAuthorizationCode(request, client); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidRequestError); + e.message.should.equal('Invalid parameter: `code`'); + } + }); + it('should throw an error if `authorizationCode` is missing', () => { + const client = {}; + const model = { + getAuthorizationCode: () => { }, + revokeAuthorizationCode: () => { }, + saveToken: () => { }, + }; + const grantType = new grant_types_1.AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + return grantType + .getAuthorizationCode(request, client) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.InvalidGrantError); + e.message.should.equal('Invalid grant: authorization code is invalid'); + }); + }); + it('should throw an error if `authorizationCode.client` is missing', () => { + const client = {}; + const model = { + getAuthorizationCode: () => { + return { authorizationCode: 12345 }; + }, + revokeAuthorizationCode: () => { }, + saveToken: () => { }, + }; + const grantType = new grant_types_1.AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + return grantType + .getAuthorizationCode(request, client) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.ServerError); + e.message.should.equal('Server error: `getAuthorizationCode()` did not return a `client` object'); + }); + }); + it('should throw an error if `authorizationCode.expiresAt` is missing', () => { + const client = {}; + const model = { + getAuthorizationCode: () => { + return { authorizationCode: 12345, client: {}, user: {} }; + }, + revokeAuthorizationCode: () => { }, + saveToken: () => { }, + }; + const grantType = new grant_types_1.AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + return grantType + .getAuthorizationCode(request, client) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.ServerError); + e.message.should.equal('Server error: `expiresAt` must be a Date instance'); + }); + }); + it('should throw an error if `authorizationCode.user` is missing', () => { + const client = {}; + const model = { + getAuthorizationCode: () => { + return { + authorizationCode: 12345, + client: {}, + expiresAt: new Date(), + }; + }, + revokeAuthorizationCode: () => { }, + saveToken: () => { }, + }; + const grantType = new grant_types_1.AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + return grantType + .getAuthorizationCode(request, client) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.ServerError); + e.message.should.equal('Server error: `getAuthorizationCode()` did not return a `user` object'); + }); + }); + it('should throw an error if the client id does not match', () => { + const client = { id: 123 }; + const model = { + getAuthorizationCode() { + return { + authorizationCode: 12345, + expiresAt: new Date(), + client: { id: 456 }, + user: {}, + }; + }, + revokeAuthorizationCode() { }, + saveToken() { }, + }; + const grantType = new grant_types_1.AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + return grantType + .getAuthorizationCode(request, client) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.InvalidGrantError); + e.message.should.equal('Invalid grant: authorization code is invalid'); + }); + }); + it('should throw an error if the auth code is expired', () => { + const client = { id: 123 }; + const date = new Date(new Date().getTime() / 2); + const model = { + getAuthorizationCode() { + return { + authorizationCode: 12345, + client: { id: 123 }, + expiresAt: date, + user: {}, + }; + }, + revokeAuthorizationCode() { }, + saveToken() { }, + }; + const grantType = new grant_types_1.AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + return grantType + .getAuthorizationCode(request, client) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.InvalidGrantError); + e.message.should.equal('Invalid grant: authorization code has expired'); + }); + }); + it('should throw an error if the `redirectUri` is invalid', () => { + const authorizationCode = { + authorizationCode: 12345, + client: { id: 'foobar' }, + expiresAt: new Date(new Date().getTime() * 2), + redirectUri: 'foobar', + user: {}, + }; + const client = { id: 'foobar' }; + const model = { + getAuthorizationCode() { + return authorizationCode; + }, + revokeAuthorizationCode() { }, + saveToken() { }, + }; + const grantType = new grant_types_1.AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + return grantType + .getAuthorizationCode(request, client) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.InvalidGrantError); + e.message.should.equal('Invalid grant: `redirect_uri` is not a valid URI'); + }); + }); + it('should return an auth code', () => { + const authorizationCode = { + authorizationCode: 12345, + client: { id: 'foobar' }, + expiresAt: new Date(new Date().getTime() * 2), + user: {}, + }; + const client = { id: 'foobar' }; + const model = { + getAuthorizationCode() { + return authorizationCode; + }, + revokeAuthorizationCode() { }, + saveToken() { }, + }; + const grantType = new grant_types_1.AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + return grantType + .getAuthorizationCode(request, client) + .then(data => { + data.should.equal(authorizationCode); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + it('should support promises', () => { + const authorizationCode = { + authorizationCode: 12345, + client: { id: 'foobar' }, + expiresAt: new Date(new Date().getTime() * 2), + user: {}, + }; + const client = { id: 'foobar' }; + const model = { + getAuthorizationCode() { + return Promise.resolve(authorizationCode); + }, + revokeAuthorizationCode() { }, + saveToken() { }, + }; + const grantType = new grant_types_1.AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + grantType + .getAuthorizationCode(request, client) + .should.be.an.instanceOf(Promise); + }); + it('should support non-promises', () => { + const authorizationCode = { + authorizationCode: 12345, + client: { id: 'foobar' }, + expiresAt: new Date(new Date().getTime() * 2), + user: {}, + }; + const client = { id: 'foobar' }; + const model = { + getAuthorizationCode() { + return authorizationCode; + }, + revokeAuthorizationCode() { }, + saveToken() { }, + }; + const grantType = new grant_types_1.AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + grantType + .getAuthorizationCode(request, client) + .should.be.an.instanceOf(Promise); + }); + }); + describe('validateRedirectUri()', () => { + it('should throw an error if `redirectUri` is missing', () => { + const authorizationCode = { + authorizationCode: 12345, + client: {}, + expiresAt: new Date(new Date().getTime() / 2), + redirectUri: 'http://foo.bar', + user: {}, + }; + const model = { + getAuthorizationCode() { }, + revokeAuthorizationCode() { + return authorizationCode; + }, + saveToken() { }, + }; + const grantType = new grant_types_1.AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + grantType.validateRedirectUri(request, authorizationCode); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidRequestError); + e.message.should.equal('Invalid request: `redirect_uri` is not a valid URI'); + } + }); + it('should throw an error if `redirectUri` is invalid', () => { + const authorizationCode = { + authorizationCode: 12345, + client: {}, + expiresAt: new Date(new Date().getTime() / 2), + redirectUri: 'http://foo.bar', + user: {}, + }; + const model = { + getAuthorizationCode() { }, + revokeAuthorizationCode() { + return true; + }, + saveToken() { }, + }; + const grantType = new grant_types_1.AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: { code: 12345, redirect_uri: 'http://bar.foo' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + grantType.validateRedirectUri(request, authorizationCode); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidRequestError); + e.message.should.equal('Invalid request: `redirect_uri` is invalid'); + } + }); + }); + describe('revokeAuthorizationCode()', () => { + it('should revoke the auth code', async () => { + const authorizationCode = { + authorizationCode: 12345, + client: {}, + expiresAt: new Date(new Date().getTime() / 2), + user: {}, + }; + const model = { + getAuthorizationCode() { }, + revokeAuthorizationCode() { + return true; + }, + saveToken() { }, + }; + const grantType = new grant_types_1.AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + try { + const data = await grantType.revokeAuthorizationCode(authorizationCode); + data.should.equal(authorizationCode); + } + catch (error) { + should.fail('should.fail', ''); + } + }); + it('should throw an error when the auth code is invalid', () => { + const authorizationCode = { + authorizationCode: 12345, + client: {}, + expiresAt: new Date(new Date().getTime() / 2), + user: {}, + }; + const model = { + getAuthorizationCode() { }, + revokeAuthorizationCode() { + return false; + }, + saveToken() { }, + }; + const grantType = new grant_types_1.AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + return grantType + .revokeAuthorizationCode(authorizationCode) + .then(data => { + data.should.equal(authorizationCode); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.InvalidGrantError); + e.message.should.equal('Invalid grant: authorization code is invalid'); + }); + }); + it('should support promises', () => { + const authorizationCode = { + authorizationCode: 12345, + client: {}, + expiresAt: new Date(new Date().getTime() / 2), + user: {}, + }; + const model = { + getAuthorizationCode() { }, + revokeAuthorizationCode() { + return Promise.resolve(true); + }, + saveToken() { }, + }; + const grantType = new grant_types_1.AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + grantType + .revokeAuthorizationCode(authorizationCode) + .should.be.an.instanceOf(Promise); + }); + it('should support non-promises', () => { + const authorizationCode = { + authorizationCode: 12345, + client: {}, + expiresAt: new Date(new Date().getTime() / 2), + user: {}, + }; + const model = { + getAuthorizationCode() { }, + revokeAuthorizationCode() { + return authorizationCode; + }, + saveToken() { }, + }; + const grantType = new grant_types_1.AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + grantType + .revokeAuthorizationCode(authorizationCode) + .should.be.an.instanceOf(Promise); + }); + }); + describe('saveToken()', () => { + it('should save the token', async () => { + const token = {}; + const model = { + getAuthorizationCode() { }, + revokeAuthorizationCode() { }, + saveToken() { + return token; + }, + validateScope() { + return 'foo'; + }, + }; + const grantType = new grant_types_1.AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + try { + const data = await grantType.saveToken({}, {}, token, ''); + data.should.equal(token); + } + catch (error) { + should.fail('should.fail', ''); + } + }); + it('should support promises', () => { + const token = {}; + const model = { + getAuthorizationCode() { }, + revokeAuthorizationCode() { }, + saveToken() { + return Promise.resolve(token); + }, + }; + const grantType = new grant_types_1.AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + grantType + .saveToken({}, {}, token, '') + .should.be.an.instanceOf(Promise); + }); + it('should support non-promises', () => { + const token = {}; + const model = { + getAuthorizationCode() { }, + revokeAuthorizationCode() { }, + saveToken() { + return token; + }, + }; + const grantType = new grant_types_1.AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + grantType + .saveToken({}, {}, token, '') + .should.be.an.instanceOf(Promise); + }); + }); +}); +//# sourceMappingURL=authorization-code-grant-type.spec.js.map \ No newline at end of file diff --git a/dist/test/integration/grant-types/authorization-code-grant-type.spec.js.map b/dist/test/integration/grant-types/authorization-code-grant-type.spec.js.map new file mode 100644 index 000000000..6fde75d3b --- /dev/null +++ b/dist/test/integration/grant-types/authorization-code-grant-type.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"authorization-code-grant-type.spec.js","sourceRoot":"","sources":["../../../../test/integration/grant-types/authorization-code-grant-type.spec.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/test/integration/grant-types/client-credentials-grant-type.spec.d.ts b/dist/test/integration/grant-types/client-credentials-grant-type.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/dist/test/integration/grant-types/client-credentials-grant-type.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/test/integration/grant-types/client-credentials-grant-type.spec.js b/dist/test/integration/grant-types/client-credentials-grant-type.spec.js new file mode 100644 index 000000000..cc38c5a71 --- /dev/null +++ b/dist/test/integration/grant-types/client-credentials-grant-type.spec.js @@ -0,0 +1,301 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const should = require("should"); +const errors_1 = require("../../../lib/errors"); +const grant_types_1 = require("../../../lib/grant-types"); +const request_1 = require("../../../lib/request"); +describe('ClientCredentialsGrantType integration', () => { + describe('constructor()', () => { + it('should throw an error if `model` is missing', () => { + try { + new grant_types_1.ClientCredentialsGrantType({ accessTokenLifetime: 3600 }); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `model`'); + } + }); + it('should throw an error if the model does not implement `getUserFromClient()`', () => { + try { + new grant_types_1.ClientCredentialsGrantType({ + accessTokenLifetime: 3600, + model: {}, + }); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Invalid argument: model does not implement `getUserFromClient()`'); + } + }); + it('should throw an error if the model does not implement `saveToken()`', () => { + try { + const model = { + getUserFromClient() { }, + }; + new grant_types_1.ClientCredentialsGrantType({ accessTokenLifetime: 3600, model }); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Invalid argument: model does not implement `saveToken()`'); + } + }); + }); + describe('handle()', () => { + it('should throw an error if `request` is missing', async () => { + const model = { + getUserFromClient() { }, + saveToken() { }, + }; + const grantType = new grant_types_1.ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + try { + await grantType.handle(undefined, undefined); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `request`'); + } + }); + it('should throw an error if `client` is missing', async () => { + const model = { + getUserFromClient() { }, + saveToken() { }, + }; + const grantType = new grant_types_1.ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await grantType.handle(request, undefined); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `client`'); + } + }); + it('should return a token', () => { + const token = {}; + const model = { + getUserFromClient() { + return {}; + }, + saveToken() { + return token; + }, + validateScope() { + return 'foo'; + }, + }; + const grantType = new grant_types_1.ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + return grantType + .handle(request, {}) + .then(data => { + data.should.equal(token); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + it('should support promises', () => { + const token = {}; + const model = { + getUserFromClient() { + return {}; + }, + saveToken() { + return token; + }, + }; + const grantType = new grant_types_1.ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + grantType.handle(request, {}).should.be.an.instanceOf(Promise); + }); + it('should support non-promises', () => { + const token = {}; + const model = { + getUserFromClient() { + return {}; + }, + saveToken() { + return token; + }, + }; + const grantType = new grant_types_1.ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + grantType.handle(request, {}).should.be.an.instanceOf(Promise); + }); + }); + describe('getUserFromClient()', () => { + it('should throw an error if `user` is missing', () => { + const model = { + getUserFromClient() { }, + saveToken() { }, + }; + const grantType = new grant_types_1.ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + return grantType + .getUserFromClient({}) + .then(() => { + should.fail('should.fail', ''); + }) + .catch((e) => { + e.should.be.an.instanceOf(errors_1.InvalidGrantError); + e.message.should.equal('Invalid grant: user credentials are invalid'); + }); + }); + it('should return a user', async () => { + const user = { email: 'foo@bar.com' }; + const model = { + getUserFromClient() { + return user; + }, + saveToken() { }, + }; + const grantType = new grant_types_1.ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + const data = await grantType.getUserFromClient({}); + data.should.equal(user); + } + catch (error) { + should.fail('should.fail', ''); + } + }); + it('should support promises', () => { + const user = { email: 'foo@bar.com' }; + const model = { + getUserFromClient() { + return Promise.resolve(user); + }, + saveToken() { }, + }; + const grantType = new grant_types_1.ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + grantType.getUserFromClient({}).should.be.an.instanceOf(Promise); + }); + it('should support non-promises', () => { + const user = { email: 'foo@bar.com' }; + const model = { + getUserFromClient() { + return user; + }, + saveToken() { }, + }; + const grantType = new grant_types_1.ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + grantType.getUserFromClient({}).should.be.an.instanceOf(Promise); + }); + }); + describe('saveToken()', () => { + it('should save the token', async () => { + const token = {}; + const model = { + getUserFromClient() { }, + saveToken() { + return token; + }, + validateScope() { + return 'foo'; + }, + }; + const grantType = new grant_types_1.ClientCredentialsGrantType({ + accessTokenLifetime: 123, + model, + }); + try { + const data = await grantType.saveToken({}, {}, token); + data.should.equal(token); + } + catch (error) { + should.fail('should.fail', ''); + } + }); + it('should support promises', () => { + const token = {}; + const model = { + getUserFromClient() { }, + saveToken() { + return Promise.resolve(token); + }, + }; + const grantType = new grant_types_1.ClientCredentialsGrantType({ + accessTokenLifetime: 123, + model, + }); + grantType + .saveToken({}, {}, token) + .should.be.an.instanceOf(Promise); + }); + }); +}); +//# sourceMappingURL=client-credentials-grant-type.spec.js.map \ No newline at end of file diff --git a/dist/test/integration/grant-types/client-credentials-grant-type.spec.js.map b/dist/test/integration/grant-types/client-credentials-grant-type.spec.js.map new file mode 100644 index 000000000..b6fca7a93 --- /dev/null +++ b/dist/test/integration/grant-types/client-credentials-grant-type.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"client-credentials-grant-type.spec.js","sourceRoot":"","sources":["../../../../test/integration/grant-types/client-credentials-grant-type.spec.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/test/integration/grant-types/implicit-grant-type.spec.d.ts b/dist/test/integration/grant-types/implicit-grant-type.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/dist/test/integration/grant-types/implicit-grant-type.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/test/integration/grant-types/implicit-grant-type.spec.js b/dist/test/integration/grant-types/implicit-grant-type.spec.js new file mode 100644 index 000000000..45a8d2920 --- /dev/null +++ b/dist/test/integration/grant-types/implicit-grant-type.spec.js @@ -0,0 +1,205 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const should = require("should"); +const errors_1 = require("../../../lib/errors"); +const grant_types_1 = require("../../../lib/grant-types"); +const request_1 = require("../../../lib/request"); +describe('ImplicitGrantType integration', () => { + describe('constructor()', () => { + it('should throw an error if `model` is missing', () => { + try { + new grant_types_1.ImplicitGrantType({ accessTokenLifetime: 3600 }); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `model`'); + } + }); + it('should throw an error if the model does not implement `saveToken()`', () => { + try { + const model = {}; + new grant_types_1.ImplicitGrantType({ model, accessTokenLifetime: 3600 }); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Invalid argument: model does not implement `saveToken()`'); + } + }); + it('should throw an error if the `user` parameter is missing', () => { + try { + const model = { + saveToken() { }, + }; + new grant_types_1.ImplicitGrantType({ model, accessTokenLifetime: 3600 }); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `user`'); + } + }); + }); + describe('handle()', () => { + it('should throw an error if `request` is missing', async () => { + const model = { + saveToken() { }, + }; + const grantType = new grant_types_1.ImplicitGrantType({ + accessTokenLifetime: 123, + model, + user: {}, + }); + try { + await grantType.handle(); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `request`'); + } + }); + it('should throw an error if `client` is missing', async () => { + const model = { + saveToken() { }, + }; + const grantType = new grant_types_1.ImplicitGrantType({ + accessTokenLifetime: 123, + model, + user: {}, + }); + const request = new request_1.Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await grantType.handle(request, undefined); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `client`'); + } + }); + it('should return a token', () => { + const client = { id: 'foobar' }; + const token = { accessToken: 'foobar-token' }; + const model = { + saveToken() { + return token; + }, + validateScope() { + return 'foo'; + }, + }; + const grantType = new grant_types_1.ImplicitGrantType({ + accessTokenLifetime: 123, + model, + user: {}, + }); + const request = new request_1.Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + return grantType + .handle(request, client) + .then(data => { + data.should.equal(token); + }) + .catch(should.fail); + }); + it('should support promises', () => { + const client = { id: 'foobar' }; + const model = { + saveToken() { }, + }; + const grantType = new grant_types_1.ImplicitGrantType({ + accessTokenLifetime: 123, + model, + user: {}, + }); + const request = new request_1.Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + grantType.handle(request, client).should.be.an.instanceOf(Promise); + }); + it('should support non-promises', () => { + const client = { id: 'foobar' }; + const model = { + saveToken() { }, + }; + const grantType = new grant_types_1.ImplicitGrantType({ + accessTokenLifetime: 123, + model, + user: {}, + }); + const request = new request_1.Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + grantType.handle(request, client).should.be.an.instanceOf(Promise); + }); + }); + describe('saveToken()', () => { + it('should save the token', () => { + const token = {}; + const model = { + saveToken() { + return token; + }, + validateScope() { + return 'foo'; + }, + }; + const grantType = new grant_types_1.ImplicitGrantType({ + accessTokenLifetime: 123, + model, + user: {}, + }); + return grantType + .saveToken(token) + .then(data => { + data.should.equal(token); + }) + .catch(should.fail); + }); + it('should support promises', () => { + const token = {}; + const model = { + saveToken() { + return Promise.resolve(token); + }, + }; + const grantType = new grant_types_1.ImplicitGrantType({ + accessTokenLifetime: 123, + model, + user: {}, + }); + grantType.saveToken(token).should.be.an.instanceOf(Promise); + }); + it('should support non-promises', () => { + const token = {}; + const model = { + saveToken() { + return token; + }, + }; + const grantType = new grant_types_1.ImplicitGrantType({ + accessTokenLifetime: 123, + model, + user: {}, + }); + grantType.saveToken(token).should.be.an.instanceOf(Promise); + }); + }); +}); +//# sourceMappingURL=implicit-grant-type.spec.js.map \ No newline at end of file diff --git a/dist/test/integration/grant-types/implicit-grant-type.spec.js.map b/dist/test/integration/grant-types/implicit-grant-type.spec.js.map new file mode 100644 index 000000000..5c7ec3259 --- /dev/null +++ b/dist/test/integration/grant-types/implicit-grant-type.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"implicit-grant-type.spec.js","sourceRoot":"","sources":["../../../../test/integration/grant-types/implicit-grant-type.spec.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/test/integration/grant-types/password-grant-type.spec.d.ts b/dist/test/integration/grant-types/password-grant-type.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/dist/test/integration/grant-types/password-grant-type.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/test/integration/grant-types/password-grant-type.spec.js b/dist/test/integration/grant-types/password-grant-type.spec.js new file mode 100644 index 000000000..81c7f7a74 --- /dev/null +++ b/dist/test/integration/grant-types/password-grant-type.spec.js @@ -0,0 +1,390 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const should = require("should"); +const errors_1 = require("../../../lib/errors"); +const grant_types_1 = require("../../../lib/grant-types"); +const request_1 = require("../../../lib/request"); +describe('PasswordGrantType integration', () => { + describe('constructor()', () => { + it('should throw an error if `model` is missing', () => { + try { + new grant_types_1.PasswordGrantType({ accessTokenLifetime: 3600 }); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `model`'); + } + }); + it('should throw an error if the model does not implement `getUser()`', () => { + try { + new grant_types_1.PasswordGrantType({ accessTokenLifetime: 3600, model: {} }); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Invalid argument: model does not implement `getUser()`'); + } + }); + it('should throw an error if the model does not implement `saveToken()`', () => { + try { + const model = { + getUser: () => { }, + }; + new grant_types_1.PasswordGrantType({ accessTokenLifetime: 3600, model }); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Invalid argument: model does not implement `saveToken()`'); + } + }); + }); + describe('handle()', () => { + it('should throw an error if `request` is missing', async () => { + const model = { + getUser: () => { }, + saveToken: () => { }, + }; + const grantType = new grant_types_1.PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + try { + await grantType.handle(undefined, undefined); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `request`'); + } + }); + it('should throw an error if `client` is missing', async () => { + const model = { + getUser: () => { }, + saveToken: () => { }, + }; + const grantType = new grant_types_1.PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + try { + await grantType.handle({}, undefined); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `client`'); + } + }); + it('should return a token', () => { + const client = { id: 'foobar' }; + const token = {}; + const model = { + getUser: () => { + return {}; + }, + saveToken: () => { + return token; + }, + validateScope: () => { + return 'baz'; + }, + }; + const grantType = new grant_types_1.PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: { username: 'foo', password: 'bar', scope: 'baz' }, + headers: {}, + method: 'ANY', + query: {}, + }); + return grantType + .handle(request, client) + .then(data => { + data.should.equal(token); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + it('should support promises', () => { + const client = { id: 'foobar' }; + const token = {}; + const model = { + getUser() { + return {}; + }, + saveToken() { + return Promise.resolve(token); + }, + }; + const grantType = new grant_types_1.PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: { username: 'foo', password: 'bar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + grantType.handle(request, client).should.be.an.instanceOf(Promise); + }); + it('should support non-promises', () => { + const client = { id: 'foobar' }; + const token = {}; + const model = { + getUser() { + return {}; + }, + saveToken() { + return token; + }, + }; + const grantType = new grant_types_1.PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: { username: 'foo', password: 'bar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + grantType.handle(request, client).should.be.an.instanceOf(Promise); + }); + }); + describe('getUser()', () => { + it('should throw an error if the request body does not contain `username`', async () => { + const model = { + getUser() { }, + saveToken() { }, + }; + const grantType = new grant_types_1.PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await grantType.getUser(request); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidRequestError); + e.message.should.equal('Missing parameter: `username`'); + } + }); + it('should throw an error if the request body does not contain `password`', async () => { + const model = { + getUser() { }, + saveToken() { }, + }; + const grantType = new grant_types_1.PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: { username: 'foo' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await grantType.getUser(request); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidRequestError); + e.message.should.equal('Missing parameter: `password`'); + } + }); + it('should throw an error if `username` is invalid', async () => { + const model = { + getUser() { }, + saveToken() { }, + }; + const grantType = new grant_types_1.PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: { username: '\r\n', password: 'foobar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await grantType.getUser(request); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidRequestError); + e.message.should.equal('Invalid parameter: `username`'); + } + }); + it('should throw an error if `password` is invalid', async () => { + const model = { + getUser() { }, + saveToken() { }, + }; + const grantType = new grant_types_1.PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: { username: 'foobar', password: '\r\n' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await grantType.getUser(request); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidRequestError); + e.message.should.equal('Invalid parameter: `password`'); + } + }); + it('should throw an error if `user` is missing', async () => { + const model = { + getUser() { }, + saveToken() { }, + }; + const grantType = new grant_types_1.PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: { username: 'foo', password: 'bar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await grantType.getUser(request); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidGrantError); + e.message.should.equal('Invalid grant: user credentials are invalid'); + } + }); + it('should return a user', async () => { + const user = { email: 'foo@bar.com' }; + const model = { + getUser() { + return user; + }, + saveToken() { }, + }; + const grantType = new grant_types_1.PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: { username: 'foo', password: 'bar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + const data = await grantType.getUser(request); + data.should.equal(user); + } + catch (error) { + should.fail('should.fail', ''); + } + }); + it('should support promises', () => { + const user = { email: 'foo@bar.com' }; + const model = { + getUser() { + return Promise.resolve(user); + }, + saveToken() { }, + }; + const grantType = new grant_types_1.PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: { username: 'foo', password: 'bar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + grantType.getUser(request).should.be.an.instanceOf(Promise); + }); + it('should support non-promises', () => { + const user = { email: 'foo@bar.com' }; + const model = { + getUser() { + return user; + }, + saveToken() { }, + }; + const grantType = new grant_types_1.PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: { username: 'foo', password: 'bar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + grantType.getUser(request).should.be.an.instanceOf(Promise); + }); + }); + describe('saveToken()', () => { + it('should save the token', async () => { + const token = {}; + const model = { + getUser() { }, + saveToken() { + return token; + }, + validateScope() { + return 'foo'; + }, + }; + const grantType = new grant_types_1.PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + try { + const data = await grantType.saveToken({}, {}, token); + data.should.equal(token); + } + catch (error) { + should.fail('should.fail', ''); + } + }); + it('should support promises', () => { + const token = {}; + const model = { + getUser() { }, + saveToken() { + return Promise.resolve(token); + }, + }; + const grantType = new grant_types_1.PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + grantType + .saveToken({}, {}, token) + .should.be.an.instanceOf(Promise); + }); + }); +}); +//# sourceMappingURL=password-grant-type.spec.js.map \ No newline at end of file diff --git a/dist/test/integration/grant-types/password-grant-type.spec.js.map b/dist/test/integration/grant-types/password-grant-type.spec.js.map new file mode 100644 index 000000000..1d62ccc55 --- /dev/null +++ b/dist/test/integration/grant-types/password-grant-type.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"password-grant-type.spec.js","sourceRoot":"","sources":["../../../../test/integration/grant-types/password-grant-type.spec.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/test/integration/grant-types/refresh-token-grant-type.spec.d.ts b/dist/test/integration/grant-types/refresh-token-grant-type.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/dist/test/integration/grant-types/refresh-token-grant-type.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/test/integration/grant-types/refresh-token-grant-type.spec.js b/dist/test/integration/grant-types/refresh-token-grant-type.spec.js new file mode 100644 index 000000000..88d2d1745 --- /dev/null +++ b/dist/test/integration/grant-types/refresh-token-grant-type.spec.js @@ -0,0 +1,652 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const should = require("should"); +const errors_1 = require("../../../lib/errors"); +const grant_types_1 = require("../../../lib/grant-types"); +const request_1 = require("../../../lib/request"); +describe('RefreshTokenGrantType integration', () => { + describe('constructor()', () => { + it('should throw an error if `model` is missing', () => { + try { + new grant_types_1.RefreshTokenGrantType({ accessTokenLifetime: 3600 }); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `model`'); + } + }); + it('should throw an error if the model does not implement `getRefreshToken()`', () => { + try { + new grant_types_1.RefreshTokenGrantType({ accessTokenLifetime: 3600, model: {} }); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Invalid argument: model does not implement `getRefreshToken()`'); + } + }); + it('should throw an error if the model does not implement `revokeToken()`', () => { + try { + const model = { + getRefreshToken() { }, + }; + new grant_types_1.RefreshTokenGrantType({ accessTokenLifetime: 3600, model }); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Invalid argument: model does not implement `revokeToken()`'); + } + }); + it('should throw an error if the model does not implement `saveToken()`', () => { + try { + const model = { + getRefreshToken() { }, + revokeToken() { }, + }; + new grant_types_1.RefreshTokenGrantType({ accessTokenLifetime: 3600, model }); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Invalid argument: model does not implement `saveToken()`'); + } + }); + }); + describe('handle()', () => { + it('should throw an error if `request` is missing', async () => { + const model = { + getRefreshToken: () => { }, + revokeToken: () => { }, + saveToken: () => { }, + }; + const grantType = new grant_types_1.RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + try { + await grantType.handle(undefined, undefined); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `request`'); + } + }); + it('should throw an error if `client` is missing', async () => { + const model = { + getRefreshToken: () => { }, + revokeToken: () => { }, + saveToken: () => { }, + }; + const grantType = new grant_types_1.RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await grantType.handle(request, undefined); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `client`'); + } + }); + it('should return a token', () => { + const client = { id: 123 }; + const token = { accessToken: 'foo', client: { id: 123 }, user: {} }; + const model = { + getRefreshToken: () => { + return token; + }, + revokeToken: () => { + return { + accessToken: 'foo', + client: { id: 123 }, + refreshTokenExpiresAt: new Date(new Date().getTime() / 2), + user: {}, + }; + }, + saveToken: () => { + return token; + }, + }; + const grantType = new grant_types_1.RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: { refresh_token: 'foobar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + return grantType + .handle(request, client) + .then(data => { + data.should.equal(token); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + it('should support promises', () => { + const client = { id: 123 }; + const model = { + getRefreshToken() { + return Promise.resolve({ + accessToken: 'foo', + client: { id: 123 }, + user: {}, + }); + }, + revokeToken() { + return Promise.resolve({ + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date().getTime() / 2), + user: {}, + }); + }, + saveToken() { + return Promise.resolve({ accessToken: 'foo', client: {}, user: {} }); + }, + }; + const grantType = new grant_types_1.RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: { refresh_token: 'foobar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + grantType.handle(request, client).should.be.an.instanceOf(Promise); + }); + }); + describe('getRefreshToken()', () => { + it('should throw an error if the `refreshToken` parameter is missing from the request body', async () => { + const client = {}; + const model = { + getRefreshToken: () => { }, + revokeToken: () => { }, + saveToken: () => { }, + }; + const grantType = new grant_types_1.RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await grantType.getRefreshToken(request, client); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidRequestError); + e.message.should.equal('Missing parameter: `refresh_token`'); + } + }); + it('should throw an error if `refreshToken` is not found', () => { + const client = { id: 123 }; + const model = { + getRefreshToken() { + return; + }, + revokeToken() { }, + saveToken() { }, + }; + const grantType = new grant_types_1.RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { refresh_token: '12345' }, + headers: {}, + method: 'ANY', + query: {}, + }); + return grantType + .getRefreshToken(request, client) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.InvalidGrantError); + e.message.should.equal('Invalid grant: refresh token is invalid'); + }); + }); + it('should throw an error if `refreshToken.client` is missing', () => { + const client = {}; + const model = { + getRefreshToken() { + return {}; + }, + revokeToken() { }, + saveToken() { }, + }; + const grantType = new grant_types_1.RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { refresh_token: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + return grantType + .getRefreshToken(request, client) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.ServerError); + e.message.should.equal('Server error: `getRefreshToken()` did not return a `client` object'); + }); + }); + it('should throw an error if `refreshToken.user` is missing', () => { + const client = {}; + const model = { + getRefreshToken() { + return { accessToken: 'foo', client: {} }; + }, + revokeToken() { }, + saveToken() { }, + }; + const grantType = new grant_types_1.RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { refresh_token: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + return grantType + .getRefreshToken(request, client) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.ServerError); + e.message.should.equal('Server error: `getRefreshToken()` did not return a `user` object'); + }); + }); + it('should throw an error if the client id does not match', () => { + const client = { id: 123 }; + const model = { + getRefreshToken() { + return { accessToken: 'foo', client: { id: 456 }, user: {} }; + }, + revokeToken() { }, + saveToken() { }, + }; + const grantType = new grant_types_1.RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { refresh_token: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + return grantType + .getRefreshToken(request, client) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.InvalidGrantError); + e.message.should.equal('Invalid grant: refresh token is invalid'); + }); + }); + it('should throw an error if `refresh_token` contains invalid characters', async () => { + const client = {}; + const model = { + getRefreshToken() { + return { client: { id: 456 }, user: {} }; + }, + revokeToken() { }, + saveToken() { }, + }; + const grantType = new grant_types_1.RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { refresh_token: 'øå€£‰' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await grantType.getRefreshToken(request, client); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidRequestError); + e.message.should.equal('Invalid parameter: `refresh_token`'); + } + }); + it('should throw an error if `refresh_token` is missing', () => { + const client = {}; + const model = { + getRefreshToken() { + return { accessToken: 'foo', client: { id: 456 }, user: {} }; + }, + revokeToken() { }, + saveToken() { }, + }; + const grantType = new grant_types_1.RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { refresh_token: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + return grantType + .getRefreshToken(request, client) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.InvalidGrantError); + e.message.should.equal('Invalid grant: refresh token is invalid'); + }); + }); + it('should throw an error if `refresh_token` is expired', () => { + const client = { id: 123 }; + const date = new Date(new Date().getTime() / 2); + const model = { + getRefreshToken() { + return { + accessToken: 'foo', + client: { id: 123 }, + refreshTokenExpiresAt: date, + user: {}, + }; + }, + revokeToken() { }, + saveToken() { }, + }; + const grantType = new grant_types_1.RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { refresh_token: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + return grantType + .getRefreshToken(request, client) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.InvalidGrantError); + e.message.should.equal('Invalid grant: refresh token has expired'); + }); + }); + it('should throw an error if `refreshTokenExpiresAt` is not a date value', () => { + const client = { id: 123 }; + const model = { + getRefreshToken() { + return { + accessToken: 'foo', + client: { id: 123 }, + refreshTokenExpiresAt: 'stringValue', + user: {}, + }; + }, + revokeToken() { }, + saveToken() { }, + }; + const grantType = new grant_types_1.RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { refresh_token: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + return grantType + .getRefreshToken(request, client) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.ServerError); + e.message.should.equal('Server error: `refreshTokenExpiresAt` must be a Date instance'); + }); + }); + it('should return a token', () => { + const client = { id: 123 }; + const token = { accessToken: 'foo', client: { id: 123 }, user: {} }; + const model = { + getRefreshToken() { + return token; + }, + revokeToken() { }, + saveToken() { }, + }; + const grantType = new grant_types_1.RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: { refresh_token: 'foobar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + return grantType + .getRefreshToken(request, client) + .then(data => { + data.should.equal(token); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + it('should support promises', () => { + const client = { id: 123 }; + const token = { accessToken: 'foo', client: { id: 123 }, user: {} }; + const model = { + getRefreshToken() { + return Promise.resolve(token); + }, + revokeToken() { }, + saveToken() { }, + }; + const grantType = new grant_types_1.RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: { refresh_token: 'foobar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + grantType + .getRefreshToken(request, client) + .should.be.an.instanceOf(Promise); + }); + it('should support non-promises', () => { + const client = { id: 123 }; + const token = { accessToken: 'foo', client: { id: 123 }, user: {} }; + const model = { + getRefreshToken() { + return token; + }, + revokeToken() { }, + saveToken() { }, + }; + const grantType = new grant_types_1.RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new request_1.Request({ + body: { refresh_token: 'foobar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + grantType + .getRefreshToken(request, client) + .should.be.an.instanceOf(Promise); + }); + }); + describe('revokeToken()', () => { + it('should throw an error if the `token` is invalid', () => { + const model = { + getRefreshToken() { }, + revokeToken() { }, + saveToken() { }, + }; + const grantType = new grant_types_1.RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + grantType + .revokeToken({}) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.InvalidGrantError); + e.message.should.equal('Invalid grant: refresh token is invalid'); + }); + }); + it('should revoke the token', () => { + const token = { + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date().getTime() / 2), + user: {}, + }; + const model = { + getRefreshToken() { }, + revokeToken() { + return token; + }, + saveToken() { }, + }; + const grantType = new grant_types_1.RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + return grantType + .revokeToken(token) + .then(data => { + data.should.equal(token); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + it('should support promises', () => { + const token = { + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date().getTime() / 2), + user: {}, + }; + const model = { + getRefreshToken() { }, + revokeToken() { + return Promise.resolve(token); + }, + saveToken() { }, + }; + const grantType = new grant_types_1.RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + grantType.revokeToken(token).should.be.an.instanceOf(Promise); + }); + it('should support non-promises', () => { + const token = { + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date().getTime() / 2), + user: {}, + }; + const model = { + getRefreshToken() { }, + revokeToken() { + return token; + }, + saveToken() { }, + }; + const grantType = new grant_types_1.RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + grantType.revokeToken(token).should.be.an.instanceOf(Promise); + }); + }); + describe('saveToken()', () => { + it('should save the token', async () => { + const token = {}; + const model = { + getRefreshToken() { }, + revokeToken() { }, + saveToken() { + return token; + }, + }; + const grantType = new grant_types_1.RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + try { + const data = await grantType.saveToken({}, {}, token); + data.should.equal(token); + } + catch (error) { + should.fail('should.fail', ''); + } + }); + it('should support promises', () => { + const token = {}; + const model = { + getRefreshToken() { }, + revokeToken() { }, + saveToken() { + return Promise.resolve(token); + }, + }; + const grantType = new grant_types_1.RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + grantType + .saveToken({}, {}, token) + .should.be.an.instanceOf(Promise); + }); + }); +}); +//# sourceMappingURL=refresh-token-grant-type.spec.js.map \ No newline at end of file diff --git a/dist/test/integration/grant-types/refresh-token-grant-type.spec.js.map b/dist/test/integration/grant-types/refresh-token-grant-type.spec.js.map new file mode 100644 index 000000000..f2abb5d36 --- /dev/null +++ b/dist/test/integration/grant-types/refresh-token-grant-type.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"refresh-token-grant-type.spec.js","sourceRoot":"","sources":["../../../../test/integration/grant-types/refresh-token-grant-type.spec.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/test/integration/handlers/authenticate-handler.spec.d.ts b/dist/test/integration/handlers/authenticate-handler.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/dist/test/integration/handlers/authenticate-handler.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/test/integration/handlers/authenticate-handler.spec.js b/dist/test/integration/handlers/authenticate-handler.spec.js new file mode 100644 index 000000000..d80ac3784 --- /dev/null +++ b/dist/test/integration/handlers/authenticate-handler.spec.js @@ -0,0 +1,583 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const should = require("should"); +const errors_1 = require("../../../lib/errors"); +const handlers_1 = require("../../../lib/handlers"); +const request_1 = require("../../../lib/request"); +const response_1 = require("../../../lib/response"); +describe('AuthenticateHandler integration', () => { + describe('constructor()', () => { + it('should throw an error if `options.model` is missing', () => { + try { + new handlers_1.AuthenticateHandler(); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `model`'); + } + }); + it('should throw an error if the model does not implement `getAccessToken()`', () => { + try { + new handlers_1.AuthenticateHandler({ model: {} }); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Invalid argument: model does not implement `getAccessToken()`'); + } + }); + it('should throw an error if `scope` was given and `addAcceptedScopesHeader()` is missing', () => { + try { + new handlers_1.AuthenticateHandler({ + model: { getAccessToken() { } }, + scope: 'foobar', + }); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `addAcceptedScopesHeader`'); + } + }); + it('should throw an error if `scope` was given and `addAuthorizedScopesHeader()` is missing', () => { + try { + new handlers_1.AuthenticateHandler({ + addAcceptedScopesHeader: true, + model: { getAccessToken() { } }, + scope: 'foobar', + }); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `addAuthorizedScopesHeader`'); + } + }); + it('should throw an error if `scope` was given and the model does not implement `verifyScope()`', () => { + try { + new handlers_1.AuthenticateHandler({ + addAcceptedScopesHeader: true, + addAuthorizedScopesHeader: true, + model: { getAccessToken() { } }, + scope: 'foobar', + }); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Invalid argument: model does not implement `verifyScope()`'); + } + }); + it('should set the `model`', () => { + const model = { getAccessToken() { } }; + const grantType = new handlers_1.AuthenticateHandler({ model }); + grantType.model.should.equal(model); + }); + it('should set the `scope`', () => { + const model = { + getAccessToken() { }, + verifyScope() { }, + }; + const grantType = new handlers_1.AuthenticateHandler({ + addAcceptedScopesHeader: true, + addAuthorizedScopesHeader: true, + model, + scope: 'foobar', + }); + grantType.scope.should.equal('foobar'); + }); + }); + describe('handle()', () => { + it('should throw an error if `request` is missing', async () => { + const handler = new handlers_1.AuthenticateHandler({ + model: { getAccessToken() { } }, + }); + try { + await handler.handle(undefined, undefined); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Invalid argument: `request` must be an instance of Request'); + } + }); + it('should set the `WWW-Authenticate` header if an unauthorized request error is thrown', () => { + const model = { + getAccessToken() { + throw new errors_1.UnauthorizedRequestError(undefined, undefined); + }, + }; + const handler = new handlers_1.AuthenticateHandler({ model }); + const request = new request_1.Request({ + body: {}, + headers: { Authorization: 'Bearer foo' }, + method: 'ANY', + query: {}, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(() => { + response + .get('WWW-Authenticate') + .should.equal('Bearer realm="Service"'); + }); + }); + it('should throw the error if an oauth error is thrown', () => { + const model = { + getAccessToken() { + throw new errors_1.AccessDeniedError('Cannot request this access token'); + }, + }; + const handler = new handlers_1.AuthenticateHandler({ model }); + const request = new request_1.Request({ + body: {}, + headers: { Authorization: 'Bearer foo' }, + method: 'ANY', + query: {}, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.AccessDeniedError); + e.message.should.equal('Cannot request this access token'); + }); + }); + it('should throw a server error if a non-oauth error is thrown', () => { + const model = { + getAccessToken() { + throw new Error('Unhandled exception'); + }, + }; + const handler = new handlers_1.AuthenticateHandler({ model }); + const request = new request_1.Request({ + body: {}, + headers: { Authorization: 'Bearer foo' }, + method: 'ANY', + query: {}, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.ServerError); + e.message.should.equal('Unhandled exception'); + }); + }); + it('should return an access token', () => { + const accessToken = { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + const model = { + getAccessToken() { + return accessToken; + }, + verifyScope() { + return true; + }, + }; + const handler = new handlers_1.AuthenticateHandler({ + addAcceptedScopesHeader: true, + addAuthorizedScopesHeader: true, + model, + scope: 'foo', + }); + const request = new request_1.Request({ + body: {}, + headers: { Authorization: 'Bearer foo' }, + method: 'ANY', + query: {}, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + return handler + .handle(request, response) + .then(data => { + data.should.equal(accessToken); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + describe('getTokenFromRequest()', () => { + it('should throw an error if more than one authentication method is used', () => { + const handler = new handlers_1.AuthenticateHandler({ + model: { getAccessToken() { } }, + }); + const request = new request_1.Request({ + body: {}, + headers: { Authorization: 'Bearer foo' }, + method: 'ANY', + query: { access_token: 'foo' }, + }); + try { + handler.getTokenFromRequest(request); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidRequestError); + e.message.should.equal('Invalid request: only one authentication method is allowed'); + } + }); + it('should throw an error if `accessToken` is missing', () => { + const handler = new handlers_1.AuthenticateHandler({ + model: { getAccessToken() { } }, + }); + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + handler.getTokenFromRequest(request); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.UnauthorizedRequestError); + e.message.should.equal('Unauthorized request: no authentication given'); + } + }); + }); + describe('getTokenFromRequestHeader()', () => { + it('should throw an error if the token is malformed', () => { + const handler = new handlers_1.AuthenticateHandler({ + model: { getAccessToken() { } }, + }); + const request = new request_1.Request({ + body: {}, + headers: { + Authorization: 'foobar', + }, + method: 'ANY', + query: {}, + }); + try { + handler.getTokenFromRequestHeader(request); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidRequestError); + e.message.should.equal('Invalid request: malformed authorization header'); + } + }); + it('should return the bearer token', () => { + const handler = new handlers_1.AuthenticateHandler({ + model: { getAccessToken() { } }, + }); + const request = new request_1.Request({ + body: {}, + headers: { + Authorization: 'Bearer foo', + }, + method: 'ANY', + query: {}, + }); + const bearerToken = handler.getTokenFromRequestHeader(request); + bearerToken.should.equal('foo'); + }); + }); + describe('getTokenFromRequestQuery()', () => { + it('should throw an error if the query contains a token', () => { + const handler = new handlers_1.AuthenticateHandler({ + model: { getAccessToken() { } }, + }); + try { + handler.getTokenFromRequestQuery(undefined); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidRequestError); + e.message.should.equal('Invalid request: do not send bearer tokens in query URLs'); + } + }); + it('should return the bearer token if `allowBearerTokensInQueryString` is true', () => { + const handler = new handlers_1.AuthenticateHandler({ + allowBearerTokensInQueryString: true, + model: { getAccessToken() { } }, + }); + const req = { query: { access_token: 'foo' } }; + handler.getTokenFromRequestQuery(req).should.equal('foo'); + }); + }); + describe('getTokenFromRequestBody()', () => { + it('should throw an error if the method is `GET`', () => { + const handler = new handlers_1.AuthenticateHandler({ + model: { getAccessToken() { } }, + }); + const request = new request_1.Request({ + body: { access_token: 'foo' }, + headers: {}, + method: 'GET', + query: {}, + }); + try { + handler.getTokenFromRequestBody(request); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidRequestError); + e.message.should.equal('Invalid request: token may not be passed in the body when using the GET verb'); + } + }); + it('should throw an error if the media type is not `application/x-www-form-urlencoded`', () => { + const handler = new handlers_1.AuthenticateHandler({ + model: { getAccessToken() { } }, + }); + const request = new request_1.Request({ + body: { access_token: 'foo' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + handler.getTokenFromRequestBody(request); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidRequestError); + e.message.should.equal('Invalid request: content must be application/x-www-form-urlencoded'); + } + }); + it('should return the bearer token', () => { + const handler = new handlers_1.AuthenticateHandler({ + model: { getAccessToken() { } }, + }); + const request = new request_1.Request({ + body: { access_token: 'foo' }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'ANY', + query: {}, + }); + handler.getTokenFromRequestBody(request).should.equal('foo'); + }); + }); + describe('getAccessToken()', () => { + it('should throw an error if `accessToken` is missing', () => { + const model = { + getAccessToken() { }, + }; + const handler = new handlers_1.AuthenticateHandler({ model }); + return handler + .getAccessToken('foo') + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.InvalidTokenError); + e.message.should.equal('Invalid token: access token is invalid'); + }); + }); + it('should throw an error if `accessToken.user` is missing', () => { + const model = { + getAccessToken() { + return {}; + }, + }; + const handler = new handlers_1.AuthenticateHandler({ model }); + return handler + .getAccessToken('foo') + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.ServerError); + e.message.should.equal('Server error: `getAccessToken()` did not return a `user` object'); + }); + }); + it('should return an access token', () => { + const accessToken = { user: {} }; + const model = { + getAccessToken() { + return accessToken; + }, + }; + const handler = new handlers_1.AuthenticateHandler({ model }); + return handler + .getAccessToken('foo') + .then(data => { + data.should.equal(accessToken); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + it('should support promises', () => { + const model = { + getAccessToken() { + return Promise.resolve({ user: {} }); + }, + }; + const handler = new handlers_1.AuthenticateHandler({ model }); + handler.getAccessToken('foo').should.be.an.instanceOf(Promise); + }); + it('should support non-promises', () => { + const model = { + getAccessToken() { + return { user: {} }; + }, + }; + const handler = new handlers_1.AuthenticateHandler({ model }); + handler.getAccessToken('foo').should.be.an.instanceOf(Promise); + }); + }); + describe('validateAccessToken()', () => { + it('should throw an error if `accessToken` is expired', () => { + const accessToken = { + accessTokenExpiresAt: new Date(new Date().getTime() / 2), + }; + const handler = new handlers_1.AuthenticateHandler({ + model: { getAccessToken() { } }, + }); + try { + handler.validateAccessToken(accessToken); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidTokenError); + e.message.should.equal('Invalid token: access token has expired'); + } + }); + it('should return an access token', () => { + const accessToken = { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + const handler = new handlers_1.AuthenticateHandler({ + model: { getAccessToken() { } }, + }); + handler.validateAccessToken(accessToken).should.equal(accessToken); + }); + }); + describe('verifyScope()', () => { + it('should throw an error if `scope` is insufficient', () => { + const model = { + getAccessToken() { }, + verifyScope() { + return false; + }, + }; + const handler = new handlers_1.AuthenticateHandler({ + addAcceptedScopesHeader: true, + addAuthorizedScopesHeader: true, + model, + scope: 'foo', + }); + return handler + .verifyScope('foo') + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.InsufficientScopeError); + e.message.should.equal('Insufficient scope: authorized scope is insufficient'); + }); + }); + it('should support promises', () => { + const model = { + getAccessToken() { }, + verifyScope() { + return true; + }, + }; + const handler = new handlers_1.AuthenticateHandler({ + addAcceptedScopesHeader: true, + addAuthorizedScopesHeader: true, + model, + scope: 'foo', + }); + handler.verifyScope('foo').should.be.an.instanceOf(Promise); + }); + it('should support non-promises', () => { + const model = { + getAccessToken() { }, + verifyScope() { + return true; + }, + }; + const handler = new handlers_1.AuthenticateHandler({ + addAcceptedScopesHeader: true, + addAuthorizedScopesHeader: true, + model, + scope: 'foo', + }); + handler.verifyScope('foo').should.be.an.instanceOf(Promise); + }); + }); + describe('updateResponse()', () => { + it('should not set the `X-Accepted-OAuth-Scopes` header if `scope` is not specified', () => { + const model = { + getAccessToken() { }, + verifyScope() { }, + }; + const handler = new handlers_1.AuthenticateHandler({ + addAcceptedScopesHeader: true, + addAuthorizedScopesHeader: false, + model, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + handler.updateResponse(response, { scope: 'foo biz' }); + response.headers.should.not.have.property('x-accepted-oauth-scopes'); + }); + it('should set the `X-Accepted-OAuth-Scopes` header if `scope` is specified', () => { + const model = { + getAccessToken() { }, + verifyScope() { }, + }; + const handler = new handlers_1.AuthenticateHandler({ + addAcceptedScopesHeader: true, + addAuthorizedScopesHeader: false, + model, + scope: 'foo bar', + }); + const response = new response_1.Response({ body: {}, headers: {} }); + handler.updateResponse(response, { scope: 'foo biz' }); + response.get('X-Accepted-OAuth-Scopes').should.equal('foo bar'); + }); + it('should not set the `X-Authorized-OAuth-Scopes` header if `scope` is not specified', () => { + const model = { + getAccessToken() { }, + verifyScope() { }, + }; + const handler = new handlers_1.AuthenticateHandler({ + addAcceptedScopesHeader: false, + addAuthorizedScopesHeader: true, + model, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + handler.updateResponse(response, { scope: 'foo biz' }); + response.headers.should.not.have.property('x-oauth-scopes'); + }); + it('should set the `X-Authorized-OAuth-Scopes` header', () => { + const model = { + getAccessToken() { }, + verifyScope() { }, + }; + const handler = new handlers_1.AuthenticateHandler({ + addAcceptedScopesHeader: false, + addAuthorizedScopesHeader: true, + model, + scope: 'foo bar', + }); + const response = new response_1.Response({ body: {}, headers: {} }); + handler.updateResponse(response, { scope: 'foo biz' }); + response.get('X-OAuth-Scopes').should.equal('foo biz'); + }); + }); +}); +//# sourceMappingURL=authenticate-handler.spec.js.map \ No newline at end of file diff --git a/dist/test/integration/handlers/authenticate-handler.spec.js.map b/dist/test/integration/handlers/authenticate-handler.spec.js.map new file mode 100644 index 000000000..09a800e74 --- /dev/null +++ b/dist/test/integration/handlers/authenticate-handler.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"authenticate-handler.spec.js","sourceRoot":"","sources":["../../../../test/integration/handlers/authenticate-handler.spec.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/test/integration/handlers/authorize-handler.spec.d.ts b/dist/test/integration/handlers/authorize-handler.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/dist/test/integration/handlers/authorize-handler.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/test/integration/handlers/authorize-handler.spec.js b/dist/test/integration/handlers/authorize-handler.spec.js new file mode 100644 index 000000000..024164d4c --- /dev/null +++ b/dist/test/integration/handlers/authorize-handler.spec.js @@ -0,0 +1,1113 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const should = require("should"); +const url = require("url"); +const errors_1 = require("../../../lib/errors"); +const handlers_1 = require("../../../lib/handlers"); +const request_1 = require("../../../lib/request"); +const response_1 = require("../../../lib/response"); +const response_types_1 = require("../../../lib/response-types"); +describe('AuthorizeHandler integration', () => { + describe('constructor()', () => { + it('should throw an error if `options.model` is missing', () => { + try { + new handlers_1.AuthorizeHandler({ authorizationCodeLifetime: 120 }); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `model`'); + } + }); + it('should throw an error if the model does not implement `getClient()`', () => { + try { + new handlers_1.AuthorizeHandler({ authorizationCodeLifetime: 120, model: {} }); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Invalid argument: model does not implement `getClient()`'); + } + }); + it('should throw an error if the model does not implement `getAccessToken()`', () => { + const model = { + getClient: () => { }, + saveAuthorizationCode: () => { }, + }; + try { + new handlers_1.AuthorizeHandler({ authorizationCodeLifetime: 120, model }); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Invalid argument: model does not implement `getAccessToken()`'); + } + }); + it('should set the `authenticateHandler`', () => { + const model = { + getAccessToken: () => { }, + getClient: () => { }, + saveAuthorizationCode: () => { }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + handler.authenticateHandler.should.be.an.instanceOf(handlers_1.AuthenticateHandler); + }); + it('should set the `model`', () => { + const model = { + getAccessToken: () => { }, + getClient: () => { }, + saveAuthorizationCode: () => { }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + handler.model.should.equal(model); + }); + }); + describe('handle()', () => { + it('should throw an error if `request` is missing', async () => { + const model = { + getAccessToken: () => { }, + getClient: () => { }, + saveAuthorizationCode: () => { }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + try { + await handler.handle(undefined, undefined); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Invalid argument: `request` must be an instance of Request'); + } + }); + it('should throw an error if `response` is missing', async () => { + const model = { + getAccessToken: () => { }, + getClient: () => { }, + saveAuthorizationCode: () => { }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await handler.handle(request, undefined); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Invalid argument: `response` must be an instance of Response'); + } + }); + it('should throw an error if `allowed` is `false`', () => { + const model = { + getAccessToken: () => { }, + getClient: () => { }, + saveAuthorizationCode: () => { }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'ANY', + query: { allowed: 'false' }, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.AccessDeniedError); + e.message.should.equal('Access denied: user denied access to application'); + }); + }); + it('should redirect to an error response if a non-oauth error is thrown', () => { + const model = { + getAccessToken: () => { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + getClient: () => { + return { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + }, + saveAuthorizationCode: () => { + throw new Error('Unhandled exception'); + }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { + client_id: 12345, + response_type: 'code', + }, + headers: { + Authorization: 'Bearer foo', + }, + method: 'ANY', + query: { + state: 'foobar', + }, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(() => { + response + .get('location') + .should.equal('http://example.com/cb?error=server_error&error_description=Unhandled%20exception&state=foobar'); + }); + }); + it('should redirect to an error response if an oauth error is thrown', () => { + const model = { + getAccessToken: () => { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + getClient: () => { + return { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + }, + saveAuthorizationCode: () => { + throw new errors_1.AccessDeniedError('Cannot request this auth code'); + }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { + client_id: 12345, + response_type: 'code', + }, + headers: { + Authorization: 'Bearer foo', + }, + method: 'ANY', + query: { + state: 'foobar', + }, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(() => { + response.get('location').should.equal('http://example.com/cb?error=access_denied&error_description=Cannot%20request%20this%20auth%20code&state=foobar'); + }); + }); + it('should redirect to a successful response with `code` and `state` if successful', () => { + const client = { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + const model = { + getAccessToken: () => { + return { + client, + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + getClient: () => { + return client; + }, + saveAuthorizationCode: () => { + return { authorizationCode: 12345, client }; + }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { + client_id: 12345, + response_type: 'code', + }, + headers: { + Authorization: 'Bearer foo', + }, + method: 'ANY', + query: { + state: 'foobar', + }, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + return handler + .handle(request, response) + .then(() => { + response + .get('location') + .should.equal('http://example.com/cb?code=12345&state=foobar'); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + it('should redirect to an error response if `scope` is invalid', () => { + const model = { + getAccessToken: () => { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + getClient: () => { + return { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + }, + saveAuthorizationCode: () => { + return {}; + }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { + client_id: 12345, + response_type: 'code', + }, + headers: { + Authorization: 'Bearer foo', + }, + method: 'ANY', + query: { + scope: [], + state: 'foobar', + }, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(() => { + response + .get('location') + .should.equal('http://example.com/cb?error=invalid_scope&error_description=Invalid%20parameter%3A%20%60scope%60'); + }); + }); + it('should redirect to an error response if `state` is missing', () => { + const model = { + getAccessToken: () => { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + getClient: () => { + return { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + }, + saveAuthorizationCode: () => { + throw new errors_1.AccessDeniedError('Cannot request this auth code'); + }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { + client_id: 12345, + response_type: 'code', + }, + headers: { + Authorization: 'Bearer foo', + }, + method: 'ANY', + query: {}, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(() => { + response + .get('location') + .should.equal('http://example.com/cb?error=invalid_request&error_description=Missing%20parameter%3A%20%60state%60'); + }); + }); + it('should redirect to an error response if `response_type` is invalid', () => { + const model = { + getAccessToken: () => { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + getClient: () => { + return { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + }, + saveAuthorizationCode: () => { + return { authorizationCode: 12345, client: {} }; + }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { + client_id: 12345, + response_type: 'test', + }, + headers: { + Authorization: 'Bearer foo', + }, + method: 'ANY', + query: { + state: 'foobar', + }, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(() => { + response.get('location').should.equal('http://example.com/cb?error=unsupported_response_type&error_description=Unsupported%20response%20type%3A%20%60response_type%60%20is%20not%20supported&state=foobar'); + }); + }); + it('should fail on invalid `response_type` before calling model.saveAuthorizationCode()', () => { + const model = { + getAccessToken: () => { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + getClient: () => { + return { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + }, + saveAuthorizationCode: () => { + throw new Error('must not be reached'); + }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { + client_id: 12345, + response_type: 'test', + }, + headers: { + Authorization: 'Bearer foo', + }, + method: 'ANY', + query: { + state: 'foobar', + }, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(() => { + response.get('location').should.equal('http://example.com/cb?error=unsupported_response_type&error_description=Unsupported%20response%20type%3A%20%60response_type%60%20is%20not%20supported&state=foobar'); + }); + }); + it('should return the `code` if successful', () => { + const client = { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + const model = { + getAccessToken: () => { + return { + client, + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + getClient() { + return client; + }, + saveAuthorizationCode() { + return { authorizationCode: 12345, client }; + }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { + client_id: 12345, + response_type: 'code', + }, + headers: { + Authorization: 'Bearer foo', + }, + method: 'ANY', + query: { + state: 'foobar', + }, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + return handler + .handle(request, response) + .then(data => { + data.should.eql({ + authorizationCode: 12345, + client, + }); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + describe('getClient()', () => { + it('should throw an error if `client_id` is missing', async () => { + const model = { + getAccessToken() { }, + getClient() { }, + saveAuthorizationCode() { }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { response_type: 'code' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await handler.getClient(request); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidRequestError); + e.message.should.equal('Missing parameter: `client_id`'); + } + }); + it('should throw an error if `client_id` is invalid', async () => { + const model = { + getAccessToken() { }, + getClient() { }, + saveAuthorizationCode() { }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { client_id: 'øå€£‰', response_type: 'code' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await handler.getClient(request); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidRequestError); + e.message.should.equal('Invalid parameter: `client_id`'); + } + }); + it('should throw an error if `client.redirectUri` is invalid', async () => { + const model = { + getAccessToken() { }, + getClient() { }, + saveAuthorizationCode() { }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { + client_id: 12345, + response_type: 'code', + redirect_uri: 'foobar', + }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await handler.getClient(request); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidRequestError); + e.message.should.equal('Invalid request: `redirect_uri` is not a valid URI'); + } + }); + it('should throw an error if `client` is missing', () => { + const model = { + getAccessToken() { }, + getClient() { }, + saveAuthorizationCode() { }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { client_id: 12345, response_type: 'code' }, + headers: {}, + method: 'ANY', + query: {}, + }); + return handler + .getClient(request) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.InvalidClientError); + e.message.should.equal('Invalid client: client credentials are invalid'); + }); + }); + it('should throw an error if `client.grants` is missing', () => { + const model = { + getAccessToken() { }, + getClient() { + return {}; + }, + saveAuthorizationCode() { }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { client_id: 12345, response_type: 'code' }, + headers: {}, + method: 'ANY', + query: {}, + }); + return handler + .getClient(request) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.InvalidClientError); + e.message.should.equal('Invalid client: missing client `grants`'); + }); + }); + it('should throw an error if `client` is unauthorized', () => { + const model = { + getAccessToken() { }, + getClient() { + return { grants: [] }; + }, + saveAuthorizationCode() { }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { client_id: 12345, response_type: 'code' }, + headers: {}, + method: 'ANY', + query: {}, + }); + return handler + .getClient(request) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.UnauthorizedClientError); + e.message.should.equal('Unauthorized client: `grant_type` is invalid'); + }); + }); + it('should throw an error if `client.redirectUri` is missing', () => { + const model = { + getAccessToken() { }, + getClient() { + return { grants: ['authorization_code'] }; + }, + saveAuthorizationCode() { }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { client_id: 12345, response_type: 'code' }, + headers: {}, + method: 'ANY', + query: {}, + }); + return handler + .getClient(request) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.InvalidClientError); + e.message.should.equal('Invalid client: missing client `redirectUri`'); + }); + }); + it('should throw an error if `client.redirectUri` is not equal to `redirectUri`', () => { + const model = { + getAccessToken() { }, + getClient() { + return { + grants: ['authorization_code'], + redirectUris: ['https://example.com'], + }; + }, + saveAuthorizationCode() { }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { + client_id: 12345, + response_type: 'code', + redirect_uri: 'https://foobar.com', + }, + headers: {}, + method: 'ANY', + query: {}, + }); + return handler + .getClient(request) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.InvalidClientError); + e.message.should.equal('Invalid client: `redirect_uri` does not match client value'); + }); + }); + it('should support promises', async () => { + const model = { + getAccessToken() { }, + async getClient() { + return { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + }, + saveAuthorizationCode() { }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { client_id: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + handler.getClient(request).should.be.an.instanceOf(Promise); + } + catch (error) { + should.fail('should.fail', ''); + } + }); + describe('with `client_id` in the request query', () => { + it('should return a client', () => { + const client = { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + const model = { + getAccessToken() { }, + getClient() { + return client; + }, + saveAuthorizationCode() { }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { response_type: 'code' }, + headers: {}, + method: 'ANY', + query: { client_id: 12345 }, + }); + return handler + .getClient(request) + .then(data => { + data.should.equal(client); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + }); + describe('getScope()', () => { + it('should throw an error if `scope` is invalid', () => { + const model = { + getAccessToken() { }, + getClient() { }, + saveAuthorizationCode() { }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { scope: 'øå€£‰' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + handler.getScope(request); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidScopeError); + e.message.should.equal('Invalid parameter: `scope`'); + } + }); + describe('with `scope` in the request body', () => { + it('should return the scope', () => { + const model = { + getAccessToken() { }, + getClient() { }, + saveAuthorizationCode() { }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { scope: 'foo' }, + headers: {}, + method: 'ANY', + query: {}, + }); + handler.getScope(request).should.equal('foo'); + }); + }); + describe('with `scope` in the request query', () => { + it('should return the scope', () => { + const model = { + getAccessToken() { }, + getClient() { }, + saveAuthorizationCode() { }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'ANY', + query: { scope: 'foo' }, + }); + handler.getScope(request).should.equal('foo'); + }); + }); + }); + describe('getState()', () => { + it('should throw an error if `allowEmptyState` is false and `state` is missing', () => { + const model = { + getAccessToken() { }, + getClient() { }, + saveAuthorizationCode() { }, + }; + const handler = new handlers_1.AuthorizeHandler({ + allowEmptyState: false, + authorizationCodeLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + handler.getState(request); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidRequestError); + e.message.should.equal('Missing parameter: `state`'); + } + }); + it('should throw an error if `state` is invalid', () => { + const model = { + getAccessToken() { }, + getClient() { }, + saveAuthorizationCode() { }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'ANY', + query: { state: 'øå€£‰' }, + }); + try { + handler.getState(request); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidRequestError); + e.message.should.equal('Invalid parameter: `state`'); + } + }); + describe('with `state` in the request body', () => { + it('should return the state', () => { + const model = { + getAccessToken() { }, + getClient() { }, + saveAuthorizationCode() { }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { state: 'foobar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + handler.getState(request).should.equal('foobar'); + }); + }); + describe('with `state` in the request query', () => { + it('should return the state', () => { + const model = { + getAccessToken() { }, + getClient() { }, + saveAuthorizationCode() { }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'ANY', + query: { state: 'foobar' }, + }); + handler.getState(request).should.equal('foobar'); + }); + }); + }); + describe('getUser()', () => { + it('should throw an error if `user` is missing', () => { + const authenticateHandler = { handle() { } }; + const model = { + getClient() { }, + saveAuthorizationCode() { }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authenticateHandler, + authorizationCodeLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + const response = new response_1.Response(); + return handler + .getUser(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.ServerError); + e.message.should.equal('Server error: `handle()` did not return a `user` object'); + }); + }); + it('should return a user', () => { + const user = {}; + const model = { + getAccessToken() { + return { + user, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + getClient() { }, + saveAuthorizationCode() { }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: {}, + headers: { Authorization: 'Bearer foo' }, + method: 'ANY', + query: {}, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + return handler + .getUser(request, response) + .then(data => { + data.should.equal(user); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + describe('buildSuccessRedirectUri()', () => { + it('should return a redirect uri', () => { + const model = { + getAccessToken() { }, + getClient() { }, + saveAuthorizationCode() { }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const responseType = new response_types_1.CodeResponseType({ + authorizationCodeLifetime: 360, + model: { saveAuthorizationCode: () => { } }, + }); + responseType.code = 12345; + const redirectUri = handler.buildSuccessRedirectUri('http://example.com/cb', responseType); + url.format(redirectUri).should.equal('http://example.com/cb?code=12345'); + }); + }); + describe('buildErrorRedirectUri()', () => { + it('should set `error_description` if available', () => { + const error = new errors_1.InvalidClientError('foo bar'); + const model = { + getAccessToken() { }, + getClient() { }, + saveAuthorizationCode() { }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const responseType = new response_types_1.CodeResponseType({ + authorizationCodeLifetime: 360, + model: { saveAuthorizationCode: () => { } }, + }); + const redirectUri = handler.buildErrorRedirectUri('http://example.com/cb', responseType, error); + url + .format(redirectUri) + .should.equal('http://example.com/cb?error=invalid_client&error_description=foo%20bar'); + }); + it('should return a redirect uri', () => { + const error = new errors_1.InvalidClientError(); + const model = { + getAccessToken() { }, + getClient() { }, + saveAuthorizationCode() { }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const responseType = new response_types_1.CodeResponseType({ + authorizationCodeLifetime: 360, + model: { saveAuthorizationCode: () => { } }, + }); + const redirectUri = handler.buildErrorRedirectUri('http://example.com/cb', responseType, error); + url + .format(redirectUri) + .should.equal('http://example.com/cb?error=invalid_client&error_description=Bad%20Request'); + }); + }); + describe('updateResponse()', () => { + it('should set the `location` header', () => { + const model = { + getAccessToken() { }, + getClient() { }, + saveAuthorizationCode() { }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const responseType = new response_types_1.CodeResponseType({ + authorizationCodeLifetime: 360, + model: { saveAuthorizationCode: () => { } }, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + const uri = url.parse('http://example.com/cb', true); + handler.updateResponse(response, uri, responseType, 'foobar'); + response + .get('location') + .should.equal('http://example.com/cb?state=foobar'); + }); + }); +}); +//# sourceMappingURL=authorize-handler.spec.js.map \ No newline at end of file diff --git a/dist/test/integration/handlers/authorize-handler.spec.js.map b/dist/test/integration/handlers/authorize-handler.spec.js.map new file mode 100644 index 000000000..04930c096 --- /dev/null +++ b/dist/test/integration/handlers/authorize-handler.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"authorize-handler.spec.js","sourceRoot":"","sources":["../../../../test/integration/handlers/authorize-handler.spec.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/test/integration/handlers/revoke-handler.spec.d.ts b/dist/test/integration/handlers/revoke-handler.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/dist/test/integration/handlers/revoke-handler.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/test/integration/handlers/revoke-handler.spec.js b/dist/test/integration/handlers/revoke-handler.spec.js new file mode 100644 index 000000000..c8f05f65a --- /dev/null +++ b/dist/test/integration/handlers/revoke-handler.spec.js @@ -0,0 +1,913 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const should = require("should"); +const util = require("util"); +const errors_1 = require("../../../lib/errors"); +const handlers_1 = require("../../../lib/handlers"); +const request_1 = require("../../../lib/request"); +const response_1 = require("../../../lib/response"); +describe('RevokeHandler integration', () => { + describe('constructor()', () => { + it('should throw an error if `options.model` is missing', () => { + try { + new handlers_1.RevokeHandler({}); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `model`'); + } + }); + it('should throw an error if the model does not implement `getClient()`', () => { + try { + new handlers_1.RevokeHandler({ model: {} }); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Invalid argument: model does not implement `getClient()`'); + } + }); + it('should set the `model`', () => { + const model = { + getClient() { }, + revokeToken() { }, + getRefreshToken() { }, + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + handler.model.should.equal(model); + }); + }); + describe('handle()', () => { + it('should throw an error if `request` is missing', async () => { + const model = { + getClient() { }, + revokeToken() { }, + getRefreshToken() { }, + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + try { + await handler.handle(); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Invalid argument: `request` must be an instance of Request'); + } + }); + it('should throw an error if `response` is missing', async () => { + const model = { + getClient() { }, + revokeToken() { }, + getRefreshToken() { }, + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await handler.handle(request); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Invalid argument: `response` must be an instance of Response'); + } + }); + it('should throw an error if the method is not `POST`', () => { + const model = { + getClient() { }, + revokeToken() { }, + getRefreshToken() { }, + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'GET', + query: {}, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + return handler + .handle(request, response) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(errors_1.InvalidRequestError); + e.message.should.equal('Invalid request: method must be POST'); + }); + }); + it('should throw an error if the media type is not `application/x-www-form-urlencoded`', () => { + const model = { + getClient() { }, + revokeToken() { }, + getRefreshToken() { }, + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'POST', + query: {}, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + return handler + .handle(request, response) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(errors_1.InvalidRequestError); + e.message.should.equal('Invalid request: content must be application/x-www-form-urlencoded'); + }); + }); + it('should throw the error if an oauth error is thrown', () => { + const model = { + getClient() { }, + revokeToken() { }, + getRefreshToken() { }, + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + const request = new request_1.Request({ + body: { token: 'hash' }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + return handler + .handle(request, response) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(errors_1.InvalidClientError); + e.message.should.equal('Invalid client: cannot retrieve client credentials'); + }); + }); + it('should throw the error if an oauth error is thrown', () => { + const model = { + getClient() { + return { grants: ['password'] }; + }, + revokeToken() { }, + getRefreshToken() { }, + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + const request = new request_1.Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + return handler + .handle(request, response) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(errors_1.InvalidRequestError); + e.message.should.equal('Missing parameter: `token`'); + }); + }); + it('should throw a server error if a non-oauth error is thrown', () => { + const model = { + getClient() { + throw new Error('Unhandled exception'); + }, + revokeToken() { }, + getRefreshToken() { }, + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + const request = new request_1.Request({ + body: { + client_id: 12345, + client_secret: 'secret', + token: 'hash', + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + return handler + .handle(request, response) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(errors_1.ServerError); + e.message.should.equal('Unhandled exception'); + e.inner.should.be.an.instanceOf(Error); + }); + }); + it('should update the response if an error is thrown', () => { + const model = { + getClient() { + throw new Error('Unhandled exception'); + }, + revokeToken() { }, + getRefreshToken() { }, + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + const request = new request_1.Request({ + body: { + client_id: 12345, + client_secret: 'secret', + grant_type: 'password', + password: 'bar', + username: 'foo', + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + return handler + .handle(request, response) + .then(should.fail) + .catch(() => { + response.body.should.eql({ + error: 'server_error', + error_description: 'Unhandled exception', + }); + response.status.should.equal(500); + }); + }); + it('should not update the response if an invalid token error is thrown', () => { + const token = { + refreshToken: 'hash', + client: {}, + user: {}, + refreshTokenExpiresAt: new Date('2015-01-01'), + }; + const client = { grants: ['password'] }; + const model = { + getClient() { + return client; + }, + revokeToken() { + return token; + }, + getRefreshToken() { }, + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + const request = new request_1.Request({ + body: { + client_id: 12345, + client_secret: 'secret', + token: 'hash', + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + return handler + .handle(request, response) + .then(should.fail) + .catch(e => { + e[0].should.be.an.instanceOf(errors_1.InvalidTokenError); + e[1].should.be.an.instanceOf(errors_1.InvalidTokenError); + response.body.should.eql({}); + response.status.should.equal(200); + }); + }); + it('should return an empty object if successful', () => { + const token = { + refreshToken: 'hash', + client: {}, + user: {}, + refreshTokenExpiresAt: new Date(Date.now() * 2), + }; + const client = { grants: ['password'] }; + const model = { + getClient() { + return client; + }, + revokeToken() { + return token; + }, + getRefreshToken() { + return token; + }, + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + const request = new request_1.Request({ + body: { + client_id: 12345, + client_secret: 'secret', + token: 'hash', + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + return handler + .handle(request, response) + .then(data => { + should.exist(data); + }) + .catch(should.fail); + }); + }); + describe('getClient()', () => { + it('should throw an error if `clientId` is invalid', async () => { + const model = { + getClient() { }, + revokeToken() { }, + getRefreshToken() { }, + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + const request = new request_1.Request({ + body: { client_id: 'øå€£‰', client_secret: 'foo' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await handler.getClient(request); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidRequestError); + e.message.should.equal('Invalid parameter: `client_id`'); + } + }); + it('should throw an error if `clientId` is invalid', async () => { + const model = { + getClient() { }, + revokeToken() { }, + getRefreshToken() { }, + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + const request = new request_1.Request({ + body: { client_id: 'foo', client_secret: 'øå€£‰' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await handler.getClient(request); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidRequestError); + e.message.should.equal('Invalid parameter: `client_secret`'); + } + }); + it('should throw an error if `client` is missing', () => { + const model = { + getClient() { }, + revokeToken() { }, + getRefreshToken() { }, + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + const request = new request_1.Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: 'ANY', + query: {}, + }); + return handler + .getClient(request) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(errors_1.InvalidClientError); + e.message.should.equal('Invalid client: client is invalid'); + }); + }); + it('should throw an error if `client.grants` is missing', () => { + const model = { + getClient() { + return {}; + }, + revokeToken() { }, + getRefreshToken() { }, + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + const request = new request_1.Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: 'ANY', + query: {}, + }); + return handler + .getClient(request) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(errors_1.ServerError); + e.message.should.equal('Server error: missing client `grants`'); + }); + }); + it('should throw a 401 error if the client is invalid and the request contains an authorization header', () => { + const model = { + getClient() { }, + revokeToken() { }, + getRefreshToken() { }, + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + const request = new request_1.Request({ + body: {}, + headers: { + authorization: util.format('Basic %s', Buffer.from('foo:bar').toString('base64')), + }, + method: 'ANY', + query: {}, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + return handler + .getClient(request, response) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(errors_1.InvalidClientError); + e.code.should.equal(401); + e.message.should.equal('Invalid client: client is invalid'); + response + .get('WWW-Authenticate') + .should.equal('Basic realm="Service"'); + }); + }); + it('should return a client', () => { + const client = { id: 12345, grants: [] }; + const model = { + getClient() { + return client; + }, + revokeToken() { }, + getRefreshToken() { }, + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + const request = new request_1.Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: 'ANY', + query: {}, + }); + return handler + .getClient(request) + .then(data => { + data.should.equal(client); + }) + .catch(should.fail); + }); + it('should support promises', () => { + const model = { + getClient() { + return Promise.resolve({ grants: [] }); + }, + revokeToken() { }, + getRefreshToken() { }, + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + const request = new request_1.Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: 'ANY', + query: {}, + }); + handler.getClient(request).should.be.an.instanceOf(Promise); + }); + it('should support non-promises', () => { + const model = { + getClient() { + return { grants: [] }; + }, + revokeToken() { }, + getRefreshToken() { }, + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + const request = new request_1.Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: 'ANY', + query: {}, + }); + handler.getClient(request).should.be.an.instanceOf(Promise); + }); + }); + describe('getClientCredentials()', () => { + it('should throw an error if `client_id` is missing', () => { + const model = { + getClient() { }, + revokeToken() { }, + getRefreshToken() { }, + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + const request = new request_1.Request({ + body: { client_secret: 'foo' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + handler.getClientCredentials(request); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidClientError); + e.message.should.equal('Invalid client: cannot retrieve client credentials'); + } + }); + it('should throw an error if `client_secret` is missing', () => { + const model = { + getClient() { }, + revokeToken() { }, + getRefreshToken() { }, + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + const request = new request_1.Request({ + body: { client_id: 'foo' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + handler.getClientCredentials(request); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidClientError); + e.message.should.equal('Invalid client: cannot retrieve client credentials'); + } + }); + describe('with `client_id` and `client_secret` in the request header as basic auth', () => { + it('should return a client', () => { + const model = { + getClient() { }, + revokeToken() { }, + getRefreshToken() { }, + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + const request = new request_1.Request({ + body: {}, + headers: { + authorization: util.format('Basic %s', Buffer.from('foo:bar').toString('base64')), + }, + method: 'ANY', + query: {}, + }); + const credentials = handler.getClientCredentials(request); + credentials.should.eql({ clientId: 'foo', clientSecret: 'bar' }); + }); + }); + describe('with `client_id` and `client_secret` in the request body', () => { + it('should return a client', () => { + const model = { + getClient() { }, + revokeToken() { }, + getRefreshToken() { }, + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + const request = new request_1.Request({ + body: { client_id: 'foo', client_secret: 'bar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + const credentials = handler.getClientCredentials(request); + credentials.should.eql({ clientId: 'foo', clientSecret: 'bar' }); + }); + }); + }); + describe('handleRevokeToken()', () => { + it('should throw an error if `token` is missing', () => { + const model = { + getClient() { }, + revokeToken() { }, + getRefreshToken() { }, + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + return handler + .handleRevokeToken(request) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(errors_1.InvalidRequestError); + e.message.should.equal('Missing parameter: `token`'); + }); + }); + it('should return a token', () => { + const client = { id: 12345, grants: ['password'] }; + const token = { + accessToken: 'hash', + client: { id: 12345 }, + accessTokenExpiresAt: new Date(Date.now() * 2), + user: {}, + }; + const model = { + getClient() { }, + revokeToken() { + return token; + }, + getRefreshToken() { }, + getAccessToken() { + return token; + }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + const request = new request_1.Request({ + body: { token: 'hash' }, + headers: {}, + method: 'ANY', + query: {}, + }); + return handler + .handleRevokeToken(request, client) + .then(data => { + should.exist(data); + }) + .catch(should.fail); + }); + it('should return a token', () => { + const client = { id: 12345, grants: ['password'] }; + const token = { + refreshToken: 'hash', + client: { id: 12345 }, + refreshTokenExpiresAt: new Date(Date.now() * 2), + user: {}, + }; + const model = { + getClient() { }, + revokeToken() { + return token; + }, + getRefreshToken() { + return token; + }, + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + const request = new request_1.Request({ + body: { token: 'hash' }, + headers: {}, + method: 'ANY', + query: {}, + }); + return handler + .handleRevokeToken(request, client) + .then(data => { + should.exist(data); + }) + .catch(should.fail); + }); + }); + describe('getRefreshToken()', () => { + it('should throw an error if the `refreshToken` is invalid', () => { + const client = {}; + const model = { + getClient() { }, + revokeToken() { }, + getRefreshToken() { }, + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + return handler + .getRefreshToken('hash', client) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(errors_1.InvalidTokenError); + e.message.should.equal('Invalid token: refresh token is invalid'); + }); + }); + it('should throw an error if the `client_id` does not match', () => { + const client = { id: 'foo' }; + const token = { + refreshToken: 'hash', + client: { id: 'baz' }, + user: {}, + refreshTokenExpiresAt: new Date(Date.now() * 2), + }; + const model = { + getClient() { }, + revokeToken() { }, + getRefreshToken() { + return token; + }, + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + return handler + .getRefreshToken('hash', client) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(errors_1.InvalidClientError); + e.message.should.equal('Invalid client: client is invalid'); + }); + }); + it('should return a token', () => { + const client = { id: 'foo' }; + const token = { + refreshToken: 'hash', + client: { id: 'foo' }, + user: {}, + refreshTokenExpiresAt: new Date(Date.now() * 2), + }; + const model = { + getClient() { }, + revokeToken() { }, + getRefreshToken() { + return token; + }, + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + return handler + .getRefreshToken('hash', client) + .then(Token => { + should.exist(Token); + }) + .catch(should.fail); + }); + }); + describe('getAccessToken()', () => { + it('should throw an error if the `accessToken` is invalid', () => { + const client = {}; + const model = { + getClient() { }, + revokeToken() { }, + getAccessToken() { }, + getRefreshToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + return handler + .getAccessToken('hash', client) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(errors_1.InvalidTokenError); + e.message.should.equal('Invalid token: access token is invalid'); + }); + }); + it('should throw an error if the `client_id` does not match', () => { + const client = { id: 'foo' }; + const token = { + accessToken: 'hash', + client: { id: 'baz' }, + user: {}, + accessTokenExpiresAt: new Date(Date.now() * 2), + }; + const model = { + getClient() { }, + revokeToken() { }, + getAccessToken() { + return token; + }, + getRefreshToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + return handler + .getAccessToken('hash', client) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(errors_1.InvalidClientError); + e.message.should.equal('Invalid client: client is invalid'); + }); + }); + it('should return a token', () => { + const client = { id: 'foo' }; + const token = { + accessToken: 'hash', + client: { id: 'foo' }, + user: {}, + accessTokenExpiresAt: new Date(Date.now() * 2), + }; + const model = { + getClient() { }, + revokeToken() { }, + getAccessToken() { + return token; + }, + getRefreshToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + return handler + .getAccessToken('hash', client) + .then(Token => { + should.exist(Token); + }) + .catch(should.fail); + }); + }); + describe('revokeToken()', () => { + it('should throw an error if the `refreshToken` is invalid', () => { + const token = 'hash'; + const client = {}; + const model = { + getClient() { }, + revokeToken() { + return false; + }, + getRefreshToken() { + return { client: {}, user: {} }; + }, + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + return handler + .revokeToken(token, client) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(errors_1.InvalidTokenError); + e.message.should.equal('Invalid token: token is invalid'); + }); + }); + }); + describe('getTokenFromRequest()', () => { + it('should throw an error if `accessToken` is missing', () => { + const model = { + getClient() { }, + revokeToken() { }, + getRefreshToken() { }, + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + handler.getTokenFromRequest(request); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidRequestError); + e.message.should.equal('Missing parameter: `token`'); + } + }); + }); + describe('updateErrorResponse()', () => { + it('should set the `body`', () => { + const error = new errors_1.AccessDeniedError('Cannot request a revoke'); + const model = { + getClient() { }, + revokeToken() { }, + getRefreshToken() { }, + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + const response = new response_1.Response({ body: {}, headers: {} }); + handler.updateErrorResponse(response, error); + response.body.error.should.equal('access_denied'); + response.body.error_description.should.equal('Cannot request a revoke'); + }); + it('should set the `status`', () => { + const error = new errors_1.AccessDeniedError('Cannot request a revoke'); + const model = { + getClient() { }, + revokeToken() { }, + getRefreshToken() { }, + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + const response = new response_1.Response({ body: {}, headers: {} }); + handler.updateErrorResponse(response, error); + response.status.should.equal(400); + }); + }); +}); +//# sourceMappingURL=revoke-handler.spec.js.map \ No newline at end of file diff --git a/dist/test/integration/handlers/revoke-handler.spec.js.map b/dist/test/integration/handlers/revoke-handler.spec.js.map new file mode 100644 index 000000000..c470b183b --- /dev/null +++ b/dist/test/integration/handlers/revoke-handler.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"revoke-handler.spec.js","sourceRoot":"","sources":["../../../../test/integration/handlers/revoke-handler.spec.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/test/integration/handlers/token-handler.spec.d.ts b/dist/test/integration/handlers/token-handler.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/dist/test/integration/handlers/token-handler.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/test/integration/handlers/token-handler.spec.js b/dist/test/integration/handlers/token-handler.spec.js new file mode 100644 index 000000000..d07cf4e14 --- /dev/null +++ b/dist/test/integration/handlers/token-handler.spec.js @@ -0,0 +1,1449 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const should = require("should"); +const util = require("util"); +const errors_1 = require("../../../lib/errors"); +const grant_types_1 = require("../../../lib/grant-types"); +const handlers_1 = require("../../../lib/handlers"); +const request_1 = require("../../../lib/request"); +const response_1 = require("../../../lib/response"); +const token_types_1 = require("../../../lib/token-types"); +describe('TokenHandler integration', () => { + describe('constructor()', () => { + it('should throw an error if `options.accessTokenLifetime` is missing', () => { + try { + new handlers_1.TokenHandler(); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `accessTokenLifetime`'); + } + }); + it('should throw an error if `options.model` is missing', () => { + try { + new handlers_1.TokenHandler({ accessTokenLifetime: 120 }); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `model`'); + } + }); + it('should throw an error if `options.refreshTokenLifetime` is missing', () => { + try { + new handlers_1.TokenHandler({ accessTokenLifetime: 120, model: {} }); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `refreshTokenLifetime`'); + } + }); + it('should throw an error if the model does not implement `getClient()`', () => { + try { + new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model: {}, + refreshTokenLifetime: 120, + }); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Invalid argument: model does not implement `getClient()`'); + } + }); + it('should set the `accessTokenLifetime`', () => { + const accessTokenLifetime = {}; + const model = { + getClient() { }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime, + model, + refreshTokenLifetime: 120, + }); + handler.accessTokenLifetime.should.equal(accessTokenLifetime); + }); + it('should set the `alwaysIssueNewRefreshToken`', () => { + const alwaysIssueNewRefreshToken = true; + const model = { + getClient() { }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 123, + model, + refreshTokenLifetime: 120, + alwaysIssueNewRefreshToken, + }); + handler.alwaysIssueNewRefreshToken.should.equal(alwaysIssueNewRefreshToken); + }); + it('should set the `alwaysIssueNewRefreshToken` to false', () => { + const alwaysIssueNewRefreshToken = false; + const model = { + getClient() { }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 123, + model, + refreshTokenLifetime: 120, + alwaysIssueNewRefreshToken, + }); + handler.alwaysIssueNewRefreshToken.should.equal(alwaysIssueNewRefreshToken); + }); + it('should return the default `alwaysIssueNewRefreshToken` value', () => { + const model = { + getClient() { }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 123, + model, + refreshTokenLifetime: 120, + }); + handler.alwaysIssueNewRefreshToken.should.equal(true); + }); + it('should set the `extendedGrantTypes`', () => { + const extendedGrantTypes = { foo: 'bar' }; + const model = { + getClient() { }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + extendedGrantTypes, + model, + refreshTokenLifetime: 120, + }); + handler.grantTypes.should.containEql(extendedGrantTypes); + }); + it('should set the `model`', () => { + const model = { + getClient() { }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + handler.model.should.equal(model); + }); + it('should set the `refreshTokenLifetime`', () => { + const refreshTokenLifetime = {}; + const model = { + getClient() { }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime, + }); + handler.refreshTokenLifetime.should.equal(refreshTokenLifetime); + }); + }); + describe('handle()', () => { + it('should throw an error if `request` is missing', async () => { + const model = { + getClient() { }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + try { + await handler.handle(undefined, undefined); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Invalid argument: `request` must be an instance of Request'); + } + }); + it('should throw an error if `response` is missing', async () => { + const model = { + getClient() { }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await handler.handle(request, undefined); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Invalid argument: `response` must be an instance of Response'); + } + }); + it('should throw an error if the method is not `POST`', () => { + const model = { + getClient() { }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'GET', + query: {}, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.InvalidRequestError); + e.message.should.equal('Invalid request: method must be POST'); + }); + }); + it('should throw an error if the media type is not `application/x-www-form-urlencoded`', () => { + const model = { + getClient() { }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'POST', + query: {}, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.InvalidRequestError); + e.message.should.equal('Invalid request: content must be application/x-www-form-urlencoded'); + }); + }); + it('should throw the error if an oauth error is thrown', () => { + const model = { + getClient() { }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new request_1.Request({ + body: {}, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.InvalidClientError); + e.message.should.equal('Invalid client: cannot retrieve client credentials'); + }); + }); + it('should throw a server error if a non-oauth error is thrown', () => { + const model = { + getClient() { + throw new Error('Unhandled exception'); + }, + getUser() { }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new request_1.Request({ + body: { + client_id: 12345, + client_secret: 'secret', + grant_type: 'password', + password: 'bar', + username: 'foo', + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.ServerError); + e.message.should.equal('Unhandled exception'); + e.inner.should.be.an.instanceOf(Error); + }); + }); + it('should update the response if an error is thrown', () => { + const model = { + getClient() { + throw new Error('Unhandled exception'); + }, + getUser() { }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new request_1.Request({ + body: { + client_id: 12345, + client_secret: 'secret', + grant_type: 'password', + password: 'bar', + username: 'foo', + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(() => { + response.body.should.eql({ + error: 'server_error', + error_description: 'Unhandled exception', + }); + response.status.should.equal(500); + }); + }); + it('should return a bearer token if successful', async () => { + const token = { + accessToken: 'foo', + client: {}, + refreshToken: 'bar', + scope: 'foobar', + user: {}, + }; + const model = { + getClient() { + return { grants: ['password'] }; + }, + getUser() { + return {}; + }, + saveToken() { + return token; + }, + validateScope() { + return 'baz'; + }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new request_1.Request({ + body: { + client_id: 12345, + client_secret: 'secret', + username: 'foo', + password: 'bar', + grant_type: 'password', + scope: 'baz', + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + try { + const data = await handler.handle(request, response); + data.should.eql(token); + } + catch (error) { + should.fail('should.fail', ''); + } + }); + it('should not return custom attributes in a bearer token if the allowExtendedTokenAttributes is not set', () => { + const token = { + accessToken: 'foo', + client: {}, + refreshToken: 'bar', + scope: 'foobar', + user: {}, + foo: 'bar', + }; + const model = { + getClient() { + return { grants: ['password'] }; + }, + getUser() { + return {}; + }, + saveToken() { + return token; + }, + validateScope() { + return 'baz'; + }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new request_1.Request({ + body: { + client_id: 12345, + client_secret: 'secret', + username: 'foo', + password: 'bar', + grant_type: 'password', + scope: 'baz', + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + return handler + .handle(request, response) + .then(() => { + should.exist(response.body.access_token); + should.exist(response.body.refresh_token); + should.exist(response.body.token_type); + should.exist(response.body.scope); + should.not.exist(response.body.foo); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + it('should return custom attributes in a bearer token if the allowExtendedTokenAttributes is set', async () => { + const token = { + accessToken: 'foo', + client: {}, + refreshToken: 'bar', + scope: 'foobar', + user: {}, + foo: 'bar', + }; + const model = { + getClient() { + return { grants: ['password'] }; + }, + getUser() { + return {}; + }, + saveToken() { + return token; + }, + validateScope() { + return 'baz'; + }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + allowExtendedTokenAttributes: true, + }); + const request = new request_1.Request({ + body: { + client_id: 12345, + client_secret: 'secret', + username: 'foo', + password: 'bar', + grant_type: 'password', + scope: 'baz', + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + await handler.handle(request, response); + should.exist(response.body.access_token); + should.exist(response.body.refresh_token); + should.exist(response.body.token_type); + should.exist(response.body.scope); + should.exist(response.body.foo); + }); + }); + describe('getClient()', () => { + it('should throw an error if `clientId` is invalid', async () => { + const model = { + getClient() { }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new request_1.Request({ + body: { client_id: 'øå€£‰', client_secret: 'foo' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await handler.getClient(request, undefined); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidRequestError); + e.message.should.equal('Invalid parameter: `client_id`'); + } + }); + it('should throw an error if `clientSecret` is invalid', async () => { + const model = { + getClient() { }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new request_1.Request({ + body: { client_id: 'foo', client_secret: 'øå€£‰' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await handler.getClient(request, undefined); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidRequestError); + e.message.should.equal('Invalid parameter: `client_secret`'); + } + }); + it('should throw an error if `client` is missing', () => { + const model = { + getClient() { }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new request_1.Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: 'ANY', + query: {}, + }); + return handler + .getClient(request, undefined) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.InvalidClientError); + e.message.should.equal('Invalid client: client is invalid'); + }); + }); + it('should throw an error if `client.grants` is missing', () => { + const model = { + getClient() { + return {}; + }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new request_1.Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: 'ANY', + query: {}, + }); + return handler + .getClient(request, undefined) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.ServerError); + e.message.should.equal('Server error: missing client `grants`'); + }); + }); + it('should throw an error if `client.grants` is invalid', async () => { + const model = { + getClient() { + return { grants: 'foobar' }; + }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new request_1.Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await handler.getClient(request, undefined); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.ServerError); + e.message.should.equal('Server error: `grants` must be an array'); + } + }); + it('should throw a 401 error if the client is invalid and the request contains an authorization header', () => { + const model = { + getClient() { }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new request_1.Request({ + body: {}, + headers: { + authorization: util.format('Basic %s', Buffer.from('foo:bar').toString('base64')), + }, + method: 'ANY', + query: {}, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + return handler + .getClient(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(errors_1.InvalidClientError); + e.code.should.equal(401); + e.message.should.equal('Invalid client: client is invalid'); + response + .get('WWW-Authenticate') + .should.equal('Basic realm="Service"'); + }); + }); + it('should return a client', async () => { + const client = { id: 12345, grants: [] }; + const model = { + getClient() { + return client; + }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new request_1.Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + const data = await handler.getClient(request, undefined); + data.should.equal(client); + } + catch (error) { + should.fail('should.fail', ''); + } + }); + describe('with `password` grant type and `requireClientAuthentication` is false', () => { + it('should return a client ', () => { + const client = { id: 12345, grants: [] }; + const model = { + async getClient() { + return client; + }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + requireClientAuthentication: { + password: false, + }, + }); + const request = new request_1.Request({ + body: { client_id: 'blah', grant_type: 'password' }, + headers: {}, + method: 'ANY', + query: {}, + }); + return handler + .getClient(request, undefined) + .then(data => { + data.should.equal(client); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + describe('with `password` grant type and `requireClientAuthentication` is false and Authorization header', () => { + it('should return a client ', () => { + const client = { id: 12345, grants: [] }; + const model = { + async getClient() { + return client; + }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + requireClientAuthentication: { + password: false, + }, + }); + const request = new request_1.Request({ + body: { grant_type: 'password' }, + headers: { + authorization: util.format('Basic %s', Buffer.from('blah:').toString('base64')), + }, + method: 'ANY', + query: {}, + }); + return handler + .getClient(request, undefined) + .then(data => { + data.should.equal(client); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + it('should support promises', () => { + const model = { + getClient() { + return Promise.resolve({ grants: [] }); + }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new request_1.Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: 'ANY', + query: {}, + }); + handler.getClient(request, undefined).should.be.an.instanceOf(Promise); + }); + it('should support non-promises', () => { + const model = { + getClient() { + return { grants: [] }; + }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new request_1.Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: 'ANY', + query: {}, + }); + handler.getClient(request, undefined).should.be.an.instanceOf(Promise); + }); + }); + describe('getClientCredentials()', () => { + it('should throw an error if `client_id` is missing', () => { + const model = { + getClient() { }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new request_1.Request({ + body: { client_secret: 'foo' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + handler.getClientCredentials(request); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidClientError); + e.message.should.equal('Invalid client: cannot retrieve client credentials'); + } + }); + it('should throw an error if `client_secret` is missing', () => { + const model = { + getClient() { }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new request_1.Request({ + body: { client_id: 'foo' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + handler.getClientCredentials(request); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidClientError); + e.message.should.equal('Invalid client: cannot retrieve client credentials'); + } + }); + describe('with `client_id` and grant type is `password` and `requireClientAuthentication` is false', () => { + it('should return a client', () => { + const model = { + getClient() { }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + requireClientAuthentication: { password: false }, + }); + const request = new request_1.Request({ + body: { client_id: 'foo', grant_type: 'password' }, + headers: {}, + method: 'ANY', + query: {}, + }); + const credentials = handler.getClientCredentials(request); + credentials.should.eql({ clientId: 'foo' }); + }); + }); + describe('with `client_id` and `client_secret` in the request header as basic auth', () => { + it('should return a client', () => { + const model = { + getClient() { }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new request_1.Request({ + body: {}, + headers: { + authorization: util.format('Basic %s', Buffer.from('foo:bar').toString('base64')), + }, + method: 'ANY', + query: {}, + }); + const credentials = handler.getClientCredentials(request); + credentials.should.eql({ clientId: 'foo', clientSecret: 'bar' }); + }); + }); + describe('with `client_id` and `client_secret` in the request body', () => { + it('should return a client', () => { + const model = { + getClient() { }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new request_1.Request({ + body: { client_id: 'foo', client_secret: 'bar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + const credentials = handler.getClientCredentials(request); + credentials.should.eql({ clientId: 'foo', clientSecret: 'bar' }); + }); + }); + }); + describe('handleGrantType()', () => { + it('should throw an error if `grant_type` is missing', async () => { + const model = { + getClient() { }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await handler.handleGrantType(request, undefined); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidRequestError); + e.message.should.equal('Missing parameter: `grant_type`'); + } + }); + it('should throw an error if `grant_type` is invalid', async () => { + const model = { + getClient() { }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new request_1.Request({ + body: { grant_type: '~foo~' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await handler.handleGrantType(request, undefined); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidRequestError); + e.message.should.equal('Invalid parameter: `grant_type`'); + } + }); + it('should throw an error if `grant_type` is unsupported', async () => { + const model = { + getClient() { }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new request_1.Request({ + body: { grant_type: 'foobar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await handler.handleGrantType(request, undefined); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.UnsupportedGrantTypeError); + e.message.should.equal('Unsupported grant type: `grant_type` is invalid'); + } + }); + it('should throw an error if `grant_type` is unauthorized', async () => { + const client = { grants: ['client_credentials'] }; + const model = { + getClient() { }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new request_1.Request({ + body: { grant_type: 'password' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await handler.handleGrantType(request, client); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.UnauthorizedClientError); + e.message.should.equal('Unauthorized client: `grant_type` is invalid'); + } + }); + describe('with grant_type `authorization_code`', () => { + it('should return a token', () => { + const client = { id: 'foobar', grants: ['authorization_code'] }; + const token = {}; + const model = { + getAuthorizationCode() { + return { + authorizationCode: 12345, + client: { id: 'foobar' }, + expiresAt: new Date(new Date().getTime() * 2), + user: {}, + }; + }, + getClient() { }, + saveToken() { + return token; + }, + validateScope() { + return 'foo'; + }, + revokeAuthorizationCode() { + return { + authorizationCode: 12345, + client: { id: 'foobar' }, + expiresAt: new Date(new Date().getTime() / 2), + user: {}, + }; + }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new request_1.Request({ + body: { + code: 12345, + grant_type: 'authorization_code', + }, + headers: {}, + method: 'ANY', + query: {}, + }); + return handler.handleGrantType(request, client).then(data => { + data.should.equal(token); + }); + }); + }); + describe('with grant_type `client_credentials`', () => { + it('should return a token', () => { + const client = { grants: ['client_credentials'] }; + const token = {}; + const model = { + getClient() { }, + getUserFromClient() { + return {}; + }, + saveToken() { + return token; + }, + validateScope() { + return 'foo'; + }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new request_1.Request({ + body: { + grant_type: 'client_credentials', + scope: 'foo', + }, + headers: {}, + method: 'ANY', + query: {}, + }); + return handler + .handleGrantType(request, client) + .then(data => { + data.should.equal(token); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + describe('with grant_type `password`', () => { + it('should return a token', () => { + const client = { grants: ['password'] }; + const token = {}; + const model = { + getClient() { }, + getUser() { + return {}; + }, + saveToken() { + return token; + }, + validateScope() { + return 'baz'; + }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new request_1.Request({ + body: { + client_id: 12345, + client_secret: 'secret', + grant_type: 'password', + password: 'bar', + username: 'foo', + scope: 'baz', + }, + headers: {}, + method: 'ANY', + query: {}, + }); + return handler + .handleGrantType(request, client) + .then(data => { + data.should.equal(token); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + describe('with grant_type `refresh_token`', () => { + it('should return a token', () => { + const client = { grants: ['refresh_token'] }; + const token = { accessToken: 'foo', client: {}, user: {} }; + const model = { + getClient() { }, + getRefreshToken() { + return { + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date().getTime() * 2), + user: {}, + }; + }, + saveToken() { + return token; + }, + revokeToken() { + return { + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date().getTime() / 2), + user: {}, + }; + }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new request_1.Request({ + body: { + grant_type: 'refresh_token', + refresh_token: 12345, + }, + headers: {}, + method: 'ANY', + query: {}, + }); + return handler + .handleGrantType(request, client) + .then(data => { + data.should.equal(token); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + describe('with custom grant_type', () => { + it('should return a token', () => { + const client = { + grants: ['urn:ietf:params:oauth:grant-type:saml2-bearer'], + }; + const token = {}; + const model = { + getClient() { }, + getUser() { + return {}; + }, + saveToken() { + return token; + }, + validateScope() { + return 'foo'; + }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + extendedGrantTypes: { + 'urn:ietf:params:oauth:grant-type:saml2-bearer': grant_types_1.PasswordGrantType, + }, + }); + const request = new request_1.Request({ + body: { + grant_type: 'urn:ietf:params:oauth:grant-type:saml2-bearer', + username: 'foo', + password: 'bar', + }, + headers: {}, + method: 'ANY', + query: {}, + }); + return handler + .handleGrantType(request, client) + .then(data => { + data.should.equal(token); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + }); + describe('getAccessTokenLifetime()', () => { + it('should return the client access token lifetime', () => { + const client = { accessTokenLifetime: 60 }; + const model = { + getClient() { + return client; + }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + handler.getAccessTokenLifetime(client).should.equal(60); + }); + it('should return the default access token lifetime', () => { + const client = {}; + const model = { + getClient() { + return client; + }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + handler.getAccessTokenLifetime(client).should.equal(120); + }); + }); + describe('getRefreshTokenLifetime()', () => { + it('should return the client access token lifetime', () => { + const client = { refreshTokenLifetime: 60 }; + const model = { + getClient() { + return client; + }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + handler.getRefreshTokenLifetime(client).should.equal(60); + }); + it('should return the default access token lifetime', () => { + const client = {}; + const model = { + getClient() { + return client; + }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + handler.getRefreshTokenLifetime(client).should.equal(120); + }); + }); + describe('getTokenType()', () => { + it('should return a token type', () => { + const model = { + getClient() { }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const tokenType = handler.getTokenType({ + accessToken: 'foo', + refreshToken: 'bar', + scope: 'foobar', + }); + tokenType.should.containEql({ + accessToken: 'foo', + accessTokenLifetime: undefined, + refreshToken: 'bar', + scope: 'foobar', + }); + }); + }); + describe('updateSuccessResponse()', () => { + it('should set the `body`', () => { + const model = { + getClient() { }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const tokenType = new token_types_1.BearerTokenType('foo', 'bar', 'biz', undefined, undefined); + const response = new response_1.Response({ body: {}, headers: {} }); + handler.updateSuccessResponse(response, tokenType); + response.body.should.eql({ + access_token: 'foo', + expires_in: 'bar', + refresh_token: 'biz', + token_type: 'Bearer', + }); + }); + it('should set the `Cache-Control` header', () => { + const model = { + getClient() { }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const tokenType = new token_types_1.BearerTokenType('foo', 'bar', 'biz', undefined, undefined); + const response = new response_1.Response({ body: {}, headers: {} }); + handler.updateSuccessResponse(response, tokenType); + response.get('Cache-Control').should.equal('no-store'); + }); + it('should set the `Pragma` header', () => { + const model = { + getClient() { }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const tokenType = new token_types_1.BearerTokenType('foo', 'bar', 'biz', undefined, undefined); + const response = new response_1.Response({ body: {}, headers: {} }); + handler.updateSuccessResponse(response, tokenType); + response.get('Pragma').should.equal('no-cache'); + }); + }); + describe('updateErrorResponse()', () => { + it('should set the `body`', () => { + const error = new errors_1.AccessDeniedError('Cannot request a token'); + const model = { + getClient() { }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + handler.updateErrorResponse(response, error); + response.body.error.should.equal('access_denied'); + response.body.error_description.should.equal('Cannot request a token'); + }); + it('should set the `status`', () => { + const error = new errors_1.AccessDeniedError('Cannot request a token'); + const model = { + getClient() { }, + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + handler.updateErrorResponse(response, error); + response.status.should.equal(400); + }); + }); +}); +//# sourceMappingURL=token-handler.spec.js.map \ No newline at end of file diff --git a/dist/test/integration/handlers/token-handler.spec.js.map b/dist/test/integration/handlers/token-handler.spec.js.map new file mode 100644 index 000000000..9d75aa401 --- /dev/null +++ b/dist/test/integration/handlers/token-handler.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"token-handler.spec.js","sourceRoot":"","sources":["../../../../test/integration/handlers/token-handler.spec.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/test/integration/request.spec.d.ts b/dist/test/integration/request.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/dist/test/integration/request.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/test/integration/request.spec.js b/dist/test/integration/request.spec.js new file mode 100644 index 000000000..02eddb1d1 --- /dev/null +++ b/dist/test/integration/request.spec.js @@ -0,0 +1,157 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const should = require("should"); +const errors_1 = require("../../lib/errors"); +const request_1 = require("../../lib/request"); +describe('Request integration', () => { + describe('constructor()', () => { + it('should throw an error if `headers` is missing', () => { + try { + new request_1.Request({ body: {} }); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `headers`'); + } + }); + it('should throw an error if `method` is missing', () => { + try { + new request_1.Request({ body: {}, headers: {} }); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `method`'); + } + }); + it('should throw an error if `query` is missing', () => { + try { + new request_1.Request({ body: {}, headers: {}, method: 'ANY' }); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `query`'); + } + }); + it('should set the `body`', () => { + const request = new request_1.Request({ + body: 'foo', + headers: {}, + method: 'ANY', + query: {}, + }); + request.body.should.equal('foo'); + }); + it('should set the `headers`', () => { + const request = new request_1.Request({ + body: {}, + headers: { foo: 'bar', QuX: 'biz' }, + method: 'ANY', + query: {}, + }); + request.headers.should.eql({ foo: 'bar', qux: 'biz' }); + }); + it('should set the `method`', () => { + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'biz', + query: {}, + }); + request.method.should.equal('BIZ'); + }); + it('should set the `query`', () => { + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'ANY', + query: 'baz', + }); + request.query.should.equal('baz'); + }); + }); + describe('get()', () => { + it('should return `undefined` if the field does not exist', () => { + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + (request.get('content-type') === undefined).should.be.true(); + }); + it('should return the value if the field exists', () => { + const request = new request_1.Request({ + body: {}, + headers: { + 'content-type': 'text/html; charset=utf-8', + }, + method: 'ANY', + query: {}, + }); + request.get('Content-Type').should.equal('text/html; charset=utf-8'); + }); + }); + describe('is()', () => { + it('should accept an array of `types`', () => { + const request = new request_1.Request({ + body: {}, + headers: { + 'content-type': 'application/json', + 'transfer-encoding': 'chunked', + }, + method: 'ANY', + query: {}, + }); + request.is(['html', 'json']).should.equal('json'); + }); + it('should accept multiple `types` as arguments', () => { + const request = new request_1.Request({ + body: {}, + headers: { + 'content-type': 'application/json', + 'transfer-encoding': 'chunked', + }, + method: 'ANY', + query: {}, + }); + request.is('html', 'json').should.equal('json'); + }); + it('should return the first matching type', () => { + const request = new request_1.Request({ + body: {}, + headers: { + 'content-type': 'text/html; charset=utf-8', + 'transfer-encoding': 'chunked', + }, + method: 'ANY', + query: {}, + }); + request.is('html').should.equal('html'); + }); + it('should return `false` if none of the `types` match', () => { + const request = new request_1.Request({ + body: {}, + headers: { + 'content-type': 'text/html; charset=utf-8', + 'transfer-encoding': 'chunked', + }, + method: 'ANY', + query: {}, + }); + request.is('json').should.be.false(); + }); + it('should return `false` if the request has no body', () => { + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + request.is('text/html').should.be.false(); + }); + }); +}); +//# sourceMappingURL=request.spec.js.map \ No newline at end of file diff --git a/dist/test/integration/request.spec.js.map b/dist/test/integration/request.spec.js.map new file mode 100644 index 000000000..6a6740395 --- /dev/null +++ b/dist/test/integration/request.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"request.spec.js","sourceRoot":"","sources":["../../../test/integration/request.spec.ts"],"names":[],"mappings":";;AAAA,iCAAiC;AACjC,6CAAwD;AACxD,+CAA4C;AAM5C,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;QAC7B,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;YACvD,IAAI;gBACF,IAAI,iBAAO,CAAC,EAAE,IAAI,EAAE,EAAE,EAAS,CAAC,CAAC;gBAEjC,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;aAChC;YAAC,OAAO,CAAC,EAAE;gBACV,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,UAAU,CAAC,6BAAoB,CAAC,CAAC;gBAChD,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;aACxD;QACH,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;YACtD,IAAI;gBACF,IAAI,iBAAO,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAS,CAAC,CAAC;gBAE9C,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;aAChC;YAAC,OAAO,CAAC,EAAE;gBACV,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,UAAU,CAAC,6BAAoB,CAAC,CAAC;gBAChD,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAC;aACvD;QACH,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;YACrD,IAAI;gBACF,IAAI,iBAAO,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAS,CAAC,CAAC;gBAE7D,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;aAChC;YAAC,OAAO,CAAC,EAAE;gBACV,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,UAAU,CAAC,6BAAoB,CAAC,CAAC;gBAChD,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;aACtD;QACH,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,uBAAuB,EAAE,GAAG,EAAE;YAC/B,MAAM,OAAO,GAAG,IAAI,iBAAO,CAAC;gBAC1B,IAAI,EAAE,KAAK;gBACX,OAAO,EAAE,EAAE;gBACX,MAAM,EAAE,KAAK;gBACb,KAAK,EAAE,EAAE;aACV,CAAC,CAAC;YAEH,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;YAClC,MAAM,OAAO,GAAG,IAAI,iBAAO,CAAC;gBAC1B,IAAI,EAAE,EAAE;gBACR,OAAO,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE;gBACnC,MAAM,EAAE,KAAK;gBACb,KAAK,EAAE,EAAE;aACV,CAAC,CAAC;YAEH,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;QACzD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,yBAAyB,EAAE,GAAG,EAAE;YACjC,MAAM,OAAO,GAAG,IAAI,iBAAO,CAAC;gBAC1B,IAAI,EAAE,EAAE;gBACR,OAAO,EAAE,EAAE;gBACX,MAAM,EAAE,KAAK;gBACb,KAAK,EAAE,EAAE;aACV,CAAC,CAAC;YAEH,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,wBAAwB,EAAE,GAAG,EAAE;YAChC,MAAM,OAAO,GAAG,IAAI,iBAAO,CAAC;gBAC1B,IAAI,EAAE,EAAE;gBACR,OAAO,EAAE,EAAE;gBACX,MAAM,EAAE,KAAK;gBACb,KAAK,EAAE,KAAK;aACb,CAAC,CAAC;YAEH,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,OAAO,EAAE,GAAG,EAAE;QACrB,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;YAC/D,MAAM,OAAO,GAAG,IAAI,iBAAO,CAAC;gBAC1B,IAAI,EAAE,EAAE;gBACR,OAAO,EAAE,EAAE;gBACX,MAAM,EAAE,KAAK;gBACb,KAAK,EAAE,EAAE;aACV,CAAC,CAAC;YAEH,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,KAAK,SAAS,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;QAC/D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;YACrD,MAAM,OAAO,GAAG,IAAI,iBAAO,CAAC;gBAC1B,IAAI,EAAE,EAAE;gBACR,OAAO,EAAE;oBACP,cAAc,EAAE,0BAA0B;iBAC3C;gBACD,MAAM,EAAE,KAAK;gBACb,KAAK,EAAE,EAAE;aACV,CAAC,CAAC;YAEH,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;QACvE,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,MAAM,EAAE,GAAG,EAAE;QACpB,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;YAC3C,MAAM,OAAO,GAAG,IAAI,iBAAO,CAAC;gBAC1B,IAAI,EAAE,EAAE;gBACR,OAAO,EAAE;oBACP,cAAc,EAAE,kBAAkB;oBAClC,mBAAmB,EAAE,SAAS;iBAC/B;gBACD,MAAM,EAAE,KAAK;gBACb,KAAK,EAAE,EAAE;aACV,CAAC,CAAC;YAEH,OAAO,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;YACrD,MAAM,OAAO,GAAG,IAAI,iBAAO,CAAC;gBAC1B,IAAI,EAAE,EAAE;gBACR,OAAO,EAAE;oBACP,cAAc,EAAE,kBAAkB;oBAClC,mBAAmB,EAAE,SAAS;iBAC/B;gBACD,MAAM,EAAE,KAAK;gBACb,KAAK,EAAE,EAAE;aACV,CAAC,CAAC;YAEH,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAClD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;YAC/C,MAAM,OAAO,GAAG,IAAI,iBAAO,CAAC;gBAC1B,IAAI,EAAE,EAAE;gBACR,OAAO,EAAE;oBACP,cAAc,EAAE,0BAA0B;oBAC1C,mBAAmB,EAAE,SAAS;iBAC/B;gBACD,MAAM,EAAE,KAAK;gBACb,KAAK,EAAE,EAAE;aACV,CAAC,CAAC;YAEH,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC1C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;YAC5D,MAAM,OAAO,GAAG,IAAI,iBAAO,CAAC;gBAC1B,IAAI,EAAE,EAAE;gBACR,OAAO,EAAE;oBACP,cAAc,EAAE,0BAA0B;oBAC1C,mBAAmB,EAAE,SAAS;iBAC/B;gBACD,MAAM,EAAE,KAAK;gBACb,KAAK,EAAE,EAAE;aACV,CAAC,CAAC;YAEH,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;QACvC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;YAC1D,MAAM,OAAO,GAAG,IAAI,iBAAO,CAAC;gBAC1B,IAAI,EAAE,EAAE;gBACR,OAAO,EAAE,EAAE;gBACX,MAAM,EAAE,KAAK;gBACb,KAAK,EAAE,EAAE;aACV,CAAC,CAAC;YAEH,OAAO,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;QAC5C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"} \ No newline at end of file diff --git a/dist/test/integration/response-types/code-response-type.spec.d.ts b/dist/test/integration/response-types/code-response-type.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/dist/test/integration/response-types/code-response-type.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/test/integration/response-types/code-response-type.spec.js b/dist/test/integration/response-types/code-response-type.spec.js new file mode 100644 index 000000000..588156384 --- /dev/null +++ b/dist/test/integration/response-types/code-response-type.spec.js @@ -0,0 +1,251 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const should = require("should"); +const sinon = require("sinon"); +const url = require("url"); +const errors_1 = require("../../../lib/errors"); +const response_types_1 = require("../../../lib/response-types"); +describe('CodeResponseType integration', () => { + describe('constructor()', () => { + it('should throw an error if `options.authorizationCodeLifetime` is missing', () => { + try { + new response_types_1.CodeResponseType(); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `authorizationCodeLifetime`'); + } + }); + it('should set the `code`', () => { + const model = { + saveAuthorizationCode: () => { }, + }; + const responseType = new response_types_1.CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + responseType.authorizationCodeLifetime.should.equal(120); + }); + }); + it('should throw an error if the model does not implement `saveAuthorizationCode()`', () => { + try { + new response_types_1.CodeResponseType({ authorizationCodeLifetime: 120, model: {} }); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Invalid argument: model does not implement `saveAuthorizationCode()`'); + } + }); + it('should set the `authorizationCodeLifetime`', () => { + const model = { + saveAuthorizationCode: () => { }, + }; + const handler = new response_types_1.CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + handler.authorizationCodeLifetime.should.equal(120); + }); + describe('buildRedirectUri()', () => { + it('should throw an error if the `redirectUri` is missing', () => { + const model = { + saveAuthorizationCode: () => { }, + }; + const responseType = new response_types_1.CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + try { + responseType.buildRedirectUri(undefined); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `redirectUri`'); + } + }); + it('should return the new redirect uri and set the `code` and `state` in the query', () => { + const model = { + saveAuthorizationCode: () => { }, + }; + const responseType = new response_types_1.CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + responseType.code = 'foo'; + const redirectUri = responseType.buildRedirectUri(url.parse('http://example.com/cb')); + url.format(redirectUri).should.equal('http://example.com/cb?code=foo'); + }); + it('should return the new redirect uri and append the `code` and `state` in the query', () => { + const model = { + saveAuthorizationCode: () => { }, + }; + const responseType = new response_types_1.CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + responseType.code = 'foo'; + const redirectUri = responseType.buildRedirectUri(url.parse('http://example.com/cb?foo=bar', true)); + url + .format(redirectUri) + .should.equal('http://example.com/cb?foo=bar&code=foo'); + }); + }); + it('should set the `model`', () => { + const model = { + saveAuthorizationCode: () => { }, + }; + const handler = new response_types_1.CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + handler.model.should.equal(model); + }); + describe('generateAuthorizationCode()', () => { + it('should return an auth code', () => { + const model = { + getAccessToken: () => { }, + getClient: () => { }, + saveAuthorizationCode: () => { }, + }; + const handler = new response_types_1.CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + return handler + .generateAuthorizationCode(undefined, undefined, undefined) + .then((data) => { + data.should.be.a.sha1(); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + it('should support promises', () => { + const model = { + generateAuthorizationCode: () => { + return Promise.resolve({}); + }, + getAccessToken: () => { }, + getClient: () => { }, + saveAuthorizationCode: () => { }, + }; + const handler = new response_types_1.CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + handler + .generateAuthorizationCode(undefined, undefined, undefined) + .should.be.an.instanceOf(Promise); + }); + }); + describe('getAuthorizationCodeExpiresAt()', () => { + it('should return a date', () => { + const model = { + getAccessToken: () => { }, + getClient: () => { }, + saveAuthorizationCode: () => { }, + }; + const handler = new response_types_1.CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + handler.getAuthorizationCodeExpiresAt({}).should.be.an.instanceOf(Date); + }); + }); + describe('saveAuthorizationCode()', () => { + it('should return an auth code', () => { + const authorizationCode = {}; + const model = { + getAccessToken: () => { }, + getClient: () => { }, + saveAuthorizationCode: () => { + return authorizationCode; + }, + }; + const handler = new response_types_1.CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + return handler + .saveAuthorizationCode('foo', 'bar', 'biz', 'baz') + .then(data => { + data.should.equal(authorizationCode); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + it('should support promises when calling `model.saveAuthorizationCode()`', () => { + const model = { + getAccessToken: () => { }, + getClient: () => { }, + saveAuthorizationCode: () => { + return Promise.resolve({}); + }, + }; + const handler = new response_types_1.CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + handler + .saveAuthorizationCode('foo', 'bar', 'biz', 'baz', undefined, undefined) + .should.be.an.instanceOf(Promise); + }); + }); + describe('saveAuthorizationCode()', () => { + it('should call `model.saveAuthorizationCode()`', () => { + const model = { + getAccessToken: () => { }, + getClient: () => { }, + saveAuthorizationCode: sinon.stub().returns({}), + }; + const handler = new response_types_1.CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + return handler + .saveAuthorizationCode('foo', 'bar', 'qux', 'biz', 'baz', 'boz') + .then(() => { + model.saveAuthorizationCode.callCount.should.equal(1); + model.saveAuthorizationCode.firstCall.args.should.have.length(3); + model.saveAuthorizationCode.firstCall.args[0].should.eql({ + authorizationCode: 'foo', + expiresAt: 'bar', + redirectUri: 'baz', + scope: 'qux', + }); + model.saveAuthorizationCode.firstCall.args[1].should.equal('biz'); + model.saveAuthorizationCode.firstCall.args[2].should.equal('boz'); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + describe('generateAuthorizationCode()', () => { + it('should call `model.generateAuthorizationCode()`', () => { + const model = { + generateAuthorizationCode: sinon.stub().returns({}), + getAccessToken: () => { }, + getClient: () => { }, + saveAuthorizationCode: () => { }, + }; + const handler = new response_types_1.CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + return handler + .generateAuthorizationCode(undefined, undefined, undefined) + .then(() => { + model.generateAuthorizationCode.callCount.should.equal(1); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); +}); +//# sourceMappingURL=code-response-type.spec.js.map \ No newline at end of file diff --git a/dist/test/integration/response-types/code-response-type.spec.js.map b/dist/test/integration/response-types/code-response-type.spec.js.map new file mode 100644 index 000000000..4ccfdde78 --- /dev/null +++ b/dist/test/integration/response-types/code-response-type.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"code-response-type.spec.js","sourceRoot":"","sources":["../../../../test/integration/response-types/code-response-type.spec.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/test/integration/response-types/token-response-type.spec.d.ts b/dist/test/integration/response-types/token-response-type.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/dist/test/integration/response-types/token-response-type.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/test/integration/response-types/token-response-type.spec.js b/dist/test/integration/response-types/token-response-type.spec.js new file mode 100644 index 000000000..c2eb9c1d0 --- /dev/null +++ b/dist/test/integration/response-types/token-response-type.spec.js @@ -0,0 +1,76 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const should = require("should"); +const url = require("url"); +const errors_1 = require("../../../lib/errors"); +const response_types_1 = require("../../../lib/response-types"); +describe('TokenResponseType integration', () => { + describe('constructor()', () => { + it('should throw an error if `options.accessTokenLifetime` is missing', () => { + try { + new response_types_1.TokenResponseType(); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `accessTokenLifetime`'); + } + }); + it('should set `accessTokenLifetime`', () => { + const responseType = new response_types_1.TokenResponseType({ + accessTokenLifetime: 120, + model: {}, + }); + responseType.accessTokenLifetime.should.equal(120); + }); + it('should set the `model`', () => { + const model = { + foobar() { }, + }; + const handler = new response_types_1.TokenResponseType({ + accessTokenLifetime: 120, + model, + }); + handler.model.should.equal(model); + }); + }); + describe('buildRedirectUri()', () => { + it('should throw an error if the `redirectUri` is missing', () => { + const responseType = new response_types_1.TokenResponseType({ + accessTokenLifetime: 120, + model: {}, + }); + try { + responseType.buildRedirectUri(undefined); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `redirectUri`'); + } + }); + it('should return the new redirect uri and set `access_token` and `state` in the query', () => { + const responseType = new response_types_1.TokenResponseType({ + accessTokenLifetime: 120, + model: {}, + }); + responseType.accessToken = 'foobar-token'; + const redirectUri = responseType.buildRedirectUri(url.parse('http://example.com/cb')); + url + .format(redirectUri) + .should.equal('http://example.com/cb#access_token=foobar-token'); + }); + it('should return the new redirect uri and append `access_token` and `state` in the query', () => { + const responseType = new response_types_1.TokenResponseType({ + accessTokenLifetime: 120, + model: {}, + }); + responseType.accessToken = 'foobar-token'; + const redirectUri = responseType.buildRedirectUri(url.parse('http://example.com/cb?foo=bar', true)); + url + .format(redirectUri) + .should.equal('http://example.com/cb?foo=bar#access_token=foobar-token'); + }); + }); +}); +//# sourceMappingURL=token-response-type.spec.js.map \ No newline at end of file diff --git a/dist/test/integration/response-types/token-response-type.spec.js.map b/dist/test/integration/response-types/token-response-type.spec.js.map new file mode 100644 index 000000000..ff68b94a1 --- /dev/null +++ b/dist/test/integration/response-types/token-response-type.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"token-response-type.spec.js","sourceRoot":"","sources":["../../../../test/integration/response-types/token-response-type.spec.ts"],"names":[],"mappings":";;AAAA,iCAAiC;AACjC,2BAA2B;AAC3B,gDAA2D;AAC3D,gEAAgE;AAMhE,QAAQ,CAAC,+BAA+B,EAAE,GAAG,EAAE;IAC7C,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;QAC7B,EAAE,CAAC,mEAAmE,EAAE,GAAG,EAAE;YAC3E,IAAI;gBACF,IAAI,kCAAiB,EAAE,CAAC;gBAExB,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;aAChC;YAAC,OAAO,CAAC,EAAE;gBACV,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,UAAU,CAAC,6BAAoB,CAAC,CAAC;gBAChD,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,0CAA0C,CAAC,CAAC;aACpE;QACH,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;YAC1C,MAAM,YAAY,GAAG,IAAI,kCAAiB,CAAC;gBACzC,mBAAmB,EAAE,GAAG;gBACxB,KAAK,EAAE,EAAE;aACV,CAAC,CAAC;YAEH,YAAY,CAAC,mBAAmB,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACrD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,wBAAwB,EAAE,GAAG,EAAE;YAChC,MAAM,KAAK,GAAG;gBACZ,MAAM,KAAI,CAAC;aACZ,CAAC;YACF,MAAM,OAAO,GAAG,IAAI,kCAAiB,CAAC;gBACpC,mBAAmB,EAAE,GAAG;gBACxB,KAAK;aACN,CAAC,CAAC;YAEH,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;QAClC,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;YAC/D,MAAM,YAAY,GAAG,IAAI,kCAAiB,CAAC;gBACzC,mBAAmB,EAAE,GAAG;gBACxB,KAAK,EAAE,EAAE;aACV,CAAC,CAAC;YAEH,IAAI;gBACF,YAAY,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC;gBAEzC,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;aAChC;YAAC,OAAO,CAAC,EAAE;gBACV,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,UAAU,CAAC,6BAAoB,CAAC,CAAC;gBAChD,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;aAC5D;QACH,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,oFAAoF,EAAE,GAAG,EAAE;YAC5F,MAAM,YAAY,GAAG,IAAI,kCAAiB,CAAC;gBACzC,mBAAmB,EAAE,GAAG;gBACxB,KAAK,EAAE,EAAE;aACV,CAAC,CAAC;YAEH,YAAY,CAAC,WAAW,GAAG,cAAc,CAAC;YAC1C,MAAM,WAAW,GAAG,YAAY,CAAC,gBAAgB,CAC/C,GAAG,CAAC,KAAK,CAAC,uBAAuB,CAAC,CACnC,CAAC;YAEF,GAAG;iBACA,MAAM,CAAC,WAAW,CAAC;iBACnB,MAAM,CAAC,KAAK,CAAC,iDAAiD,CAAC,CAAC;QACrE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,uFAAuF,EAAE,GAAG,EAAE;YAC/F,MAAM,YAAY,GAAG,IAAI,kCAAiB,CAAC;gBACzC,mBAAmB,EAAE,GAAG;gBACxB,KAAK,EAAE,EAAE;aACV,CAAC,CAAC;YAEH,YAAY,CAAC,WAAW,GAAG,cAAc,CAAC;YAC1C,MAAM,WAAW,GAAG,YAAY,CAAC,gBAAgB,CAC/C,GAAG,CAAC,KAAK,CAAC,+BAA+B,EAAE,IAAI,CAAC,CACjD,CAAC;YAEF,GAAG;iBACA,MAAM,CAAC,WAAW,CAAC;iBACnB,MAAM,CAAC,KAAK,CACX,yDAAyD,CAC1D,CAAC;QACN,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"} \ No newline at end of file diff --git a/dist/test/integration/response.spec.d.ts b/dist/test/integration/response.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/dist/test/integration/response.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/test/integration/response.spec.js b/dist/test/integration/response.spec.js new file mode 100644 index 000000000..eaaafa4db --- /dev/null +++ b/dist/test/integration/response.spec.js @@ -0,0 +1,55 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const response_1 = require("../../lib/response"); +describe('Response integration', () => { + describe('constructor()', () => { + it('should set the `body`', () => { + const response = new response_1.Response({ body: 'foo', headers: {} }); + response.body.should.equal('foo'); + }); + it('should set the `headers`', () => { + const response = new response_1.Response({ + body: {}, + headers: { foo: 'bar', QuX: 'biz' }, + }); + response.headers.should.eql({ foo: 'bar', qux: 'biz' }); + }); + it('should set the `status` to 200', () => { + const response = new response_1.Response({ body: {}, headers: {} }); + response.status.should.equal(200); + }); + }); + describe('get()', () => { + it('should return `undefined` if the field does not exist', () => { + const response = new response_1.Response({ body: {}, headers: {} }); + (response.get('content-type') === undefined).should.be.true(); + }); + it('should return the value if the field exists', () => { + const response = new response_1.Response({ + body: {}, + headers: { 'content-type': 'text/html; charset=utf-8' }, + }); + response.get('Content-Type').should.equal('text/html; charset=utf-8'); + }); + }); + describe('redirect()', () => { + it('should set the location header to `url`', () => { + const response = new response_1.Response({ body: {}, headers: {} }); + response.redirect('http://example.com'); + response.get('Location').should.equal('http://example.com'); + }); + it('should set the `status` to 302', () => { + const response = new response_1.Response({ body: {}, headers: {} }); + response.redirect('http://example.com'); + response.status.should.equal(302); + }); + }); + describe('set()', () => { + it('should set the `field`', () => { + const response = new response_1.Response({ body: {}, headers: {} }); + response.set('foo', 'bar'); + response.headers.should.eql({ foo: 'bar' }); + }); + }); +}); +//# sourceMappingURL=response.spec.js.map \ No newline at end of file diff --git a/dist/test/integration/response.spec.js.map b/dist/test/integration/response.spec.js.map new file mode 100644 index 000000000..8982e9907 --- /dev/null +++ b/dist/test/integration/response.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"response.spec.js","sourceRoot":"","sources":["../../../test/integration/response.spec.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/test/integration/server.spec.d.ts b/dist/test/integration/server.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/dist/test/integration/server.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/test/integration/server.spec.js b/dist/test/integration/server.spec.js new file mode 100644 index 000000000..62391adef --- /dev/null +++ b/dist/test/integration/server.spec.js @@ -0,0 +1,234 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const should = require("should"); +const sinon = require("sinon"); +const errors_1 = require("../../lib/errors"); +const handlers_1 = require("../../lib/handlers"); +const request_1 = require("../../lib/request"); +const response_1 = require("../../lib/response"); +const server_1 = require("../../lib/server"); +describe('Server integration', () => { + describe('constructor()', () => { + it('should throw an error if `model` is missing', () => { + try { + new server_1.OAuth2Server({}); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `model`'); + } + }); + it('should set the `model`', () => { + const model = {}; + const server = new server_1.OAuth2Server({ model }); + server.options.model.should.equal(model); + }); + }); + describe('authenticate()', () => { + it('should set the default `options`', async () => { + const model = { + getAccessToken() { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + }; + const server = new server_1.OAuth2Server({ model }); + const request = new request_1.Request({ + body: {}, + headers: { Authorization: 'Bearer foo' }, + method: 'ANY', + query: {}, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + try { + const stub = sinon + .stub(handlers_1.AuthenticateHandler.prototype, 'handle') + .returnsThis(); + const token = await server.authenticate(request, response); + token.addAcceptedScopesHeader.should.be.true(); + token.addAuthorizedScopesHeader.should.be.true(); + token.allowBearerTokensInQueryString.should.be.false(); + stub.restore(); + } + catch (error) { + should.fail('should.fail', ''); + } + }); + it('should return a promise', () => { + const model = { + async getAccessToken(token) { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + }; + const server = new server_1.OAuth2Server({ model }); + const request = new request_1.Request({ + body: {}, + headers: { Authorization: 'Bearer foo' }, + method: 'ANY', + query: {}, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + const handler = server.authenticate(request, response); + handler.should.be.an.instanceOf(Promise); + }); + }); + describe('authorize()', () => { + it('should set the default `options`', async () => { + const model = { + async getAccessToken() { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + async getClient() { + return { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + }, + async saveAuthorizationCode() { + return { authorizationCode: 123 }; + }, + }; + const server = new server_1.OAuth2Server({ model }); + const request = new request_1.Request({ + body: { + client_id: 1234, + client_secret: 'secret', + response_type: 'code', + }, + headers: { Authorization: 'Bearer foo' }, + method: 'ANY', + query: { state: 'foobar' }, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + const stub = sinon + .stub(handlers_1.AuthorizeHandler.prototype, 'handle') + .returnsThis(); + const code = await server.authorize(request, response); + const options = code.options; + options.allowEmptyState.should.be.false(); + options.authorizationCodeLifetime.should.be.equal(300); + stub.restore(); + }); + it('should return a promise', () => { + const model = { + getAccessToken() { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + getClient() { + return { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + }, + saveAuthorizationCode() { + return { authorizationCode: 123 }; + }, + }; + const server = new server_1.OAuth2Server({ model }); + const request = new request_1.Request({ + body: { + client_id: 1234, + client_secret: 'secret', + response_type: 'code', + }, + headers: { Authorization: 'Bearer foo' }, + method: 'ANY', + query: { state: 'foobar' }, + }); + const response = new response_1.Response({ body: {}, headers: {} }); + try { + const handler = server.authorize(request, response); + handler.should.be.an.instanceOf(Promise); + } + catch (error) { + should.fail('should.fail', ''); + } + }); + }); + describe('token()', () => { + it('should set the default `options`', async () => { + const model = { + async getClient() { + return { grants: ['password'] }; + }, + async getUser() { + return {}; + }, + async saveToken() { + return { accessToken: 1234, client: {}, user: {} }; + }, + async validateScope() { + return 'foo'; + }, + }; + const server = new server_1.OAuth2Server({ model }); + const request = new request_1.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_1.Response({ body: {}, headers: {} }); + const stub = sinon.stub(handlers_1.TokenHandler.prototype, 'handle').returnsThis(); + const token = await server.token(request, response); + token.accessTokenLifetime.should.equal(3600); + token.refreshTokenLifetime.should.equal(1209600); + stub.restore(); + }); + it('should return a promise', () => { + const model = { + async getClient() { + return { grants: ['password'] }; + }, + async getUser() { + return {}; + }, + async saveToken() { + return { accessToken: 1234, client: {}, user: {} }; + }, + }; + const server = new server_1.OAuth2Server({ model }); + const request = new request_1.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_1.Response({ body: {}, headers: {} }); + const handler = server.token(request, response); + handler.should.be.an.instanceOf(Promise); + }); + }); +}); +//# sourceMappingURL=server.spec.js.map \ No newline at end of file diff --git a/dist/test/integration/server.spec.js.map b/dist/test/integration/server.spec.js.map new file mode 100644 index 000000000..ac17f1d6b --- /dev/null +++ b/dist/test/integration/server.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"server.spec.js","sourceRoot":"","sources":["../../../test/integration/server.spec.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/test/integration/token-types/bearer-token-type.spec.d.ts b/dist/test/integration/token-types/bearer-token-type.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/dist/test/integration/token-types/bearer-token-type.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/test/integration/token-types/bearer-token-type.spec.js b/dist/test/integration/token-types/bearer-token-type.spec.js new file mode 100644 index 000000000..ec16075a7 --- /dev/null +++ b/dist/test/integration/token-types/bearer-token-type.spec.js @@ -0,0 +1,71 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const should = require("should"); +const errors_1 = require("../../../lib/errors"); +const token_types_1 = require("../../../lib/token-types"); +describe('BearerTokenType integration', () => { + describe('constructor()', () => { + it('should throw an error if `accessToken` is missing', () => { + try { + new token_types_1.BearerTokenType(undefined, undefined, undefined, undefined, undefined); + should.fail('should.fail', ''); + } + catch (e) { + e.should.be.an.instanceOf(errors_1.InvalidArgumentError); + e.message.should.equal('Missing parameter: `accessToken`'); + } + }); + it('should set the `accessToken`', () => { + const responseType = new token_types_1.BearerTokenType('foo', 'bar', undefined, undefined, undefined); + responseType.accessToken.should.equal('foo'); + }); + it('should set the `accessTokenLifetime`', () => { + const responseType = new token_types_1.BearerTokenType('foo', 'bar', undefined, undefined, undefined); + responseType.accessTokenLifetime.should.equal('bar'); + }); + it('should set the `refreshToken`', () => { + const responseType = new token_types_1.BearerTokenType('foo', 'bar', 'biz', undefined, undefined); + responseType.refreshToken.should.equal('biz'); + }); + }); + describe('valueOf()', () => { + it('should return the value representation', () => { + const responseType = new token_types_1.BearerTokenType('foo', 'bar', undefined, undefined, undefined); + const value = responseType.valueOf(); + value.should.eql({ + access_token: 'foo', + expires_in: 'bar', + token_type: 'Bearer', + }); + }); + it('should not include the `expires_in` if not given', () => { + const responseType = new token_types_1.BearerTokenType('foo', undefined, undefined, undefined, undefined); + const value = responseType.valueOf(); + value.should.eql({ + access_token: 'foo', + token_type: 'Bearer', + }); + }); + it('should set `refresh_token` if `refreshToken` is defined', () => { + const responseType = new token_types_1.BearerTokenType('foo', 'bar', 'biz', undefined, undefined); + const value = responseType.valueOf(); + value.should.eql({ + access_token: 'foo', + expires_in: 'bar', + refresh_token: 'biz', + token_type: 'Bearer', + }); + }); + it('should set `expires_in` if `accessTokenLifetime` is defined', () => { + const responseType = new token_types_1.BearerTokenType('foo', 'bar', 'biz', undefined, undefined); + const value = responseType.valueOf(); + value.should.eql({ + access_token: 'foo', + expires_in: 'bar', + refresh_token: 'biz', + token_type: 'Bearer', + }); + }); + }); +}); +//# sourceMappingURL=bearer-token-type.spec.js.map \ No newline at end of file diff --git a/dist/test/integration/token-types/bearer-token-type.spec.js.map b/dist/test/integration/token-types/bearer-token-type.spec.js.map new file mode 100644 index 000000000..6e7c245c5 --- /dev/null +++ b/dist/test/integration/token-types/bearer-token-type.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"bearer-token-type.spec.js","sourceRoot":"","sources":["../../../../test/integration/token-types/bearer-token-type.spec.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/test/integration/utils/token-util.spec.d.ts b/dist/test/integration/utils/token-util.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/dist/test/integration/utils/token-util.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/test/integration/utils/token-util.spec.js b/dist/test/integration/utils/token-util.spec.js new file mode 100644 index 000000000..f3d55b93e --- /dev/null +++ b/dist/test/integration/utils/token-util.spec.js @@ -0,0 +1,18 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const should = require("should"); +const TokenUtil = require("../../../lib/utils/token-util"); +describe('TokenUtil integration', () => { + describe('generateRandomToken()', () => { + it('should return a sha-1 token', async () => { + try { + const token = await TokenUtil.GenerateRandomToken(); + token.should.be.a.sha1(); + } + catch (error) { + should.fail('should.fail', ''); + } + }); + }); +}); +//# sourceMappingURL=token-util.spec.js.map \ No newline at end of file diff --git a/dist/test/integration/utils/token-util.spec.js.map b/dist/test/integration/utils/token-util.spec.js.map new file mode 100644 index 000000000..441419261 --- /dev/null +++ b/dist/test/integration/utils/token-util.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"token-util.spec.js","sourceRoot":"","sources":["../../../../test/integration/utils/token-util.spec.ts"],"names":[],"mappings":";;AAAA,iCAAiC;AACjC,2DAA2D;AAM3D,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;QACrC,EAAE,CAAC,6BAA6B,EAAE,KAAK,IAAI,EAAE;YAC3C,IAAI;gBACF,MAAM,KAAK,GAAQ,MAAM,SAAS,CAAC,mBAAmB,EAAE,CAAC;gBACzD,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;aAC1B;YAAC,OAAO,KAAK,EAAE;gBACd,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;aAChC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"} \ No newline at end of file diff --git a/dist/test/unit/grant-types/abstract-grant-type.spec.d.ts b/dist/test/unit/grant-types/abstract-grant-type.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/dist/test/unit/grant-types/abstract-grant-type.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/test/unit/grant-types/abstract-grant-type.spec.js b/dist/test/unit/grant-types/abstract-grant-type.spec.js new file mode 100644 index 000000000..246f7d016 --- /dev/null +++ b/dist/test/unit/grant-types/abstract-grant-type.spec.js @@ -0,0 +1,52 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const should = require("should"); +const sinon = require("sinon"); +const grant_types_1 = require("../../../lib/grant-types"); +describe('AbstractGrantType', () => { + describe('generateAccessToken()', () => { + it('should call `model.generateAccessToken()`', async () => { + const model = { + generateAccessToken: sinon + .stub() + .returns({ client: {}, expiresAt: new Date(), user: {} }), + }; + const handler = new grant_types_1.AbstractGrantType({ + accessTokenLifetime: 120, + model, + }); + try { + await handler.generateAccessToken(); + model.generateAccessToken.callCount.should.equal(1); + model.generateAccessToken.firstCall.thisValue.should.equal(model); + } + catch (error) { + should.fail('should.fail', ''); + } + }); + }); + describe('generateRefreshToken()', () => { + it('should call `model.generateRefreshToken()`', async () => { + const model = { + generateRefreshToken: sinon.stub().returns({ + client: {}, + expiresAt: new Date(new Date().getTime() / 2), + user: {}, + }), + }; + const handler = new grant_types_1.AbstractGrantType({ + accessTokenLifetime: 120, + model, + }); + try { + await handler.generateRefreshToken(); + model.generateRefreshToken.callCount.should.equal(1); + model.generateRefreshToken.firstCall.thisValue.should.equal(model); + } + catch (error) { + should.fail('should.fail', ''); + } + }); + }); +}); +//# sourceMappingURL=abstract-grant-type.spec.js.map \ No newline at end of file diff --git a/dist/test/unit/grant-types/abstract-grant-type.spec.js.map b/dist/test/unit/grant-types/abstract-grant-type.spec.js.map new file mode 100644 index 000000000..6590988e6 --- /dev/null +++ b/dist/test/unit/grant-types/abstract-grant-type.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"abstract-grant-type.spec.js","sourceRoot":"","sources":["../../../../test/unit/grant-types/abstract-grant-type.spec.ts"],"names":[],"mappings":";;AAAA,iCAAiC;AACjC,+BAA+B;AAC/B,0DAA6D;AAM7D,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;QACrC,EAAE,CAAC,2CAA2C,EAAE,KAAK,IAAI,EAAE;YACzD,MAAM,KAAK,GAAG;gBACZ,mBAAmB,EAAE,KAAK;qBACvB,IAAI,EAAE;qBACN,OAAO,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;aAC5D,CAAC;YACF,MAAM,OAAO,GAAG,IAAI,+BAAiB,CAAC;gBACpC,mBAAmB,EAAE,GAAG;gBACxB,KAAK;aACN,CAAC,CAAC;YACH,IAAI;gBACF,MAAM,OAAO,CAAC,mBAAmB,EAAE,CAAC;gBACpC,KAAK,CAAC,mBAAmB,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBACpD,KAAK,CAAC,mBAAmB,CAAC,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;aACnE;YAAC,OAAO,KAAK,EAAE;gBACd,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;aAChC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;QACtC,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;YAC1D,MAAM,KAAK,GAAG;gBACZ,oBAAoB,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC;oBACzC,MAAM,EAAE,EAAE;oBACV,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;oBAC7C,IAAI,EAAE,EAAE;iBACT,CAAC;aACH,CAAC;YACF,MAAM,OAAO,GAAG,IAAI,+BAAiB,CAAC;gBACpC,mBAAmB,EAAE,GAAG;gBACxB,KAAK;aACN,CAAC,CAAC;YACH,IAAI;gBACF,MAAM,OAAO,CAAC,oBAAoB,EAAE,CAAC;gBACrC,KAAK,CAAC,oBAAoB,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBACrD,KAAK,CAAC,oBAAoB,CAAC,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;aACpE;YAAC,OAAO,KAAK,EAAE;gBACd,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;aAChC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"} \ No newline at end of file diff --git a/dist/test/unit/grant-types/authorization-code-grant-type.spec.d.ts b/dist/test/unit/grant-types/authorization-code-grant-type.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/dist/test/unit/grant-types/authorization-code-grant-type.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/test/unit/grant-types/authorization-code-grant-type.spec.js b/dist/test/unit/grant-types/authorization-code-grant-type.spec.js new file mode 100644 index 000000000..428c2c46c --- /dev/null +++ b/dist/test/unit/grant-types/authorization-code-grant-type.spec.js @@ -0,0 +1,111 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const should = require("should"); +const sinon = require("sinon"); +const grant_types_1 = require("../../../lib/grant-types"); +const request_1 = require("../../../lib/request"); +describe('AuthorizationCodeGrantType', () => { + describe('getAuthorizationCode()', () => { + it('should call `model.getAuthorizationCode()`', async () => { + const model = { + getAuthorizationCode: sinon.stub().returns({ + authorizationCode: 12345, + client: {}, + expiresAt: new Date(new Date().getTime() * 2), + user: {}, + }), + revokeAuthorizationCode() { }, + saveToken() { }, + }; + const handler = new grant_types_1.AuthorizationCodeGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + const client = {}; + try { + await handler.getAuthorizationCode(request, client); + model.getAuthorizationCode.callCount.should.equal(1); + model.getAuthorizationCode.firstCall.args.should.have.length(1); + model.getAuthorizationCode.firstCall.args[0].should.equal(12345); + model.getAuthorizationCode.firstCall.thisValue.should.equal(model); + } + catch (error) { + should.fail('should.fail', ''); + } + }); + }); + describe('revokeAuthorizationCode()', () => { + it('should call `model.revokeAuthorizationCode()`', async () => { + const model = { + getAuthorizationCode() { }, + revokeAuthorizationCode: sinon.stub().returns(true), + saveToken() { }, + }; + const handler = new grant_types_1.AuthorizationCodeGrantType({ + accessTokenLifetime: 120, + model, + }); + const authorizationCode = {}; + try { + await handler.revokeAuthorizationCode(authorizationCode); + model.revokeAuthorizationCode.callCount.should.equal(1); + model.revokeAuthorizationCode.firstCall.args.should.have.length(1); + model.revokeAuthorizationCode.firstCall.args[0].should.equal(authorizationCode); + model.revokeAuthorizationCode.firstCall.thisValue.should.equal(model); + } + catch (error) { + should.fail('should.fail', ''); + } + }); + }); + describe('saveToken()', () => { + it('should call `model.saveToken()`', async () => { + const client = {}; + const user = {}; + const model = { + getAuthorizationCode() { }, + revokeAuthorizationCode() { }, + saveToken: sinon.stub().returns(true), + }; + const handler = new grant_types_1.AuthorizationCodeGrantType({ + accessTokenLifetime: 120, + model, + }); + sinon.stub(handler, 'validateScope').returns('foobiz'); + sinon + .stub(handler, 'generateAccessToken') + .returns(Promise.resolve('foo')); + sinon + .stub(handler, 'generateRefreshToken') + .returns(Promise.resolve('bar')); + sinon.stub(handler, 'getAccessTokenExpiresAt').returns('biz'); + sinon.stub(handler, 'getRefreshTokenExpiresAt').returns('baz'); + try { + await handler.saveToken(user, client, 'foobar', 'foobiz'); + model.saveToken.callCount.should.equal(1); + model.saveToken.firstCall.args.should.have.length(3); + model.saveToken.firstCall.args[0].should.eql({ + accessToken: 'foo', + authorizationCode: 'foobar', + accessTokenExpiresAt: 'biz', + refreshToken: 'bar', + refreshTokenExpiresAt: 'baz', + scope: 'foobiz', + }); + model.saveToken.firstCall.args[1].should.equal(client); + model.saveToken.firstCall.args[2].should.equal(user); + model.saveToken.firstCall.thisValue.should.equal(model); + } + catch (error) { + should.fail('should.fail', ''); + } + }); + }); +}); +//# sourceMappingURL=authorization-code-grant-type.spec.js.map \ No newline at end of file diff --git a/dist/test/unit/grant-types/authorization-code-grant-type.spec.js.map b/dist/test/unit/grant-types/authorization-code-grant-type.spec.js.map new file mode 100644 index 000000000..c08098877 --- /dev/null +++ b/dist/test/unit/grant-types/authorization-code-grant-type.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"authorization-code-grant-type.spec.js","sourceRoot":"","sources":["../../../../test/unit/grant-types/authorization-code-grant-type.spec.ts"],"names":[],"mappings":";;AAAA,iCAAiC;AACjC,+BAA+B;AAC/B,0DAAsE;AACtE,kDAA+C;AAM/C,QAAQ,CAAC,4BAA4B,EAAE,GAAG,EAAE;IAC1C,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;QACtC,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;YAC1D,MAAM,KAAK,GAAG;gBACZ,oBAAoB,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC;oBACzC,iBAAiB,EAAE,KAAK;oBACxB,MAAM,EAAE,EAAE;oBACV,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;oBAC7C,IAAI,EAAE,EAAE;iBACT,CAAC;gBACF,uBAAuB,KAAI,CAAC;gBAC5B,SAAS,KAAI,CAAC;aACf,CAAC;YACF,MAAM,OAAO,GAAG,IAAI,wCAA0B,CAAC;gBAC7C,mBAAmB,EAAE,GAAG;gBACxB,KAAK;aACN,CAAC,CAAC;YACH,MAAM,OAAO,GAAG,IAAI,iBAAO,CAAC;gBAC1B,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE;gBACrB,OAAO,EAAE,EAAE;gBACX,MAAM,EAAE,KAAK;gBACb,KAAK,EAAE,EAAE;aACV,CAAC,CAAC;YACH,MAAM,MAAM,GAAQ,EAAE,CAAC;YACvB,IAAI;gBACF,MAAM,OAAO,CAAC,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;gBAEpD,KAAK,CAAC,oBAAoB,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBACrD,KAAK,CAAC,oBAAoB,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;gBAChE,KAAK,CAAC,oBAAoB,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;gBACjE,KAAK,CAAC,oBAAoB,CAAC,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;aACpE;YAAC,OAAO,KAAK,EAAE;gBACd,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;aAChC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACzC,EAAE,CAAC,+CAA+C,EAAE,KAAK,IAAI,EAAE;YAC7D,MAAM,KAAK,GAAG;gBACZ,oBAAoB,KAAI,CAAC;gBACzB,uBAAuB,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;gBACnD,SAAS,KAAI,CAAC;aACf,CAAC;YACF,MAAM,OAAO,GAAG,IAAI,wCAA0B,CAAC;gBAC7C,mBAAmB,EAAE,GAAG;gBACxB,KAAK;aACN,CAAC,CAAC;YACH,MAAM,iBAAiB,GAAQ,EAAE,CAAC;YAClC,IAAI;gBACF,MAAM,OAAO,CAAC,uBAAuB,CAAC,iBAAiB,CAAC,CAAC;gBAEzD,KAAK,CAAC,uBAAuB,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBACxD,KAAK,CAAC,uBAAuB,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;gBACnE,KAAK,CAAC,uBAAuB,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAC1D,iBAAiB,CAClB,CAAC;gBACF,KAAK,CAAC,uBAAuB,CAAC,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;aACvE;YAAC,OAAO,KAAK,EAAE;gBACd,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;aAChC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;QAC3B,EAAE,CAAC,iCAAiC,EAAE,KAAK,IAAI,EAAE;YAC/C,MAAM,MAAM,GAAQ,EAAE,CAAC;YACvB,MAAM,IAAI,GAAG,EAAE,CAAC;YAChB,MAAM,KAAK,GAAG;gBACZ,oBAAoB,KAAI,CAAC;gBACzB,uBAAuB,KAAI,CAAC;gBAC5B,SAAS,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;aACtC,CAAC;YACF,MAAM,OAAO,GAAG,IAAI,wCAA0B,CAAC;gBAC7C,mBAAmB,EAAE,GAAG;gBACxB,KAAK;aACN,CAAC,CAAC;YAEH,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC,OAAO,CAAC,QAAe,CAAC,CAAC;YAC9D,KAAK;iBACF,IAAI,CAAC,OAAO,EAAE,qBAAqB,CAAC;iBACpC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;YACnC,KAAK;iBACF,IAAI,CAAC,OAAO,EAAE,sBAAsB,CAAC;iBACrC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;YACnC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,yBAAyB,CAAC,CAAC,OAAO,CAAC,KAAY,CAAC,CAAC;YACrE,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,0BAA0B,CAAC,CAAC,OAAO,CAAC,KAAY,CAAC,CAAC;YACtE,IAAI;gBACF,MAAM,OAAO,CAAC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;gBAC1D,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBAC1C,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;gBACrD,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;oBAC3C,WAAW,EAAE,KAAK;oBAClB,iBAAiB,EAAE,QAAQ;oBAC3B,oBAAoB,EAAE,KAAK;oBAC3B,YAAY,EAAE,KAAK;oBACnB,qBAAqB,EAAE,KAAK;oBAC5B,KAAK,EAAE,QAAQ;iBAChB,CAAC,CAAC;gBACH,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;gBACvD,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBACrD,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;aACzD;YAAC,OAAO,KAAK,EAAE;gBACd,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;aAChC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"} \ No newline at end of file diff --git a/dist/test/unit/grant-types/client-credentials-grant-type.spec.d.ts b/dist/test/unit/grant-types/client-credentials-grant-type.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/dist/test/unit/grant-types/client-credentials-grant-type.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/test/unit/grant-types/client-credentials-grant-type.spec.js b/dist/test/unit/grant-types/client-credentials-grant-type.spec.js new file mode 100644 index 000000000..ec7951bd9 --- /dev/null +++ b/dist/test/unit/grant-types/client-credentials-grant-type.spec.js @@ -0,0 +1,64 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const should = require("should"); +const sinon = require("sinon"); +const grant_types_1 = require("../../../lib/grant-types"); +describe('ClientCredentialsGrantType', () => { + describe('getUserFromClient()', () => { + it('should call `model.getUserFromClient()`', async () => { + const model = { + getUserFromClient: sinon.stub().returns(true), + saveToken() { }, + }; + const handler = new grant_types_1.ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + const client = {}; + try { + await handler.getUserFromClient(client); + model.getUserFromClient.callCount.should.equal(1); + model.getUserFromClient.firstCall.args.should.have.length(1); + model.getUserFromClient.firstCall.args[0].should.equal(client); + model.getUserFromClient.firstCall.thisValue.should.equal(model); + } + catch (error) { + should.fail('should.fail', ''); + } + }); + }); + describe('saveToken()', () => { + it('should call `model.saveToken()`', async () => { + const client = {}; + const user = {}; + const model = { + getUserFromClient() { }, + saveToken: sinon.stub().returns(true), + }; + const handler = new grant_types_1.ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + sinon.stub(handler, 'validateScope').returns('foobar'); + sinon.stub(handler, 'generateAccessToken').returns('foo'); + sinon.stub(handler, 'getAccessTokenExpiresAt').returns('biz'); + try { + await handler.saveToken(user, client, 'foobar'); + model.saveToken.callCount.should.equal(1); + model.saveToken.firstCall.args.should.have.length(3); + model.saveToken.firstCall.args[0].should.eql({ + accessToken: 'foo', + accessTokenExpiresAt: 'biz', + scope: 'foobar', + }); + model.saveToken.firstCall.args[1].should.equal(client); + model.saveToken.firstCall.args[2].should.equal(user); + model.saveToken.firstCall.thisValue.should.equal(model); + } + catch (error) { + should.fail('should.fail', ''); + } + }); + }); +}); +//# sourceMappingURL=client-credentials-grant-type.spec.js.map \ No newline at end of file diff --git a/dist/test/unit/grant-types/client-credentials-grant-type.spec.js.map b/dist/test/unit/grant-types/client-credentials-grant-type.spec.js.map new file mode 100644 index 000000000..bebfb7fd6 --- /dev/null +++ b/dist/test/unit/grant-types/client-credentials-grant-type.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"client-credentials-grant-type.spec.js","sourceRoot":"","sources":["../../../../test/unit/grant-types/client-credentials-grant-type.spec.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/test/unit/grant-types/implict-grant-type.spec.d.ts b/dist/test/unit/grant-types/implict-grant-type.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/dist/test/unit/grant-types/implict-grant-type.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/test/unit/grant-types/implict-grant-type.spec.js b/dist/test/unit/grant-types/implict-grant-type.spec.js new file mode 100644 index 000000000..79ec61fe8 --- /dev/null +++ b/dist/test/unit/grant-types/implict-grant-type.spec.js @@ -0,0 +1,42 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const should = require("should"); +const sinon = require("sinon"); +const grant_types_1 = require("../../../lib/grant-types"); +describe('ImplicitGrantType', () => { + describe('saveToken()', () => { + it('should call `model.saveToken()`', () => { + const client = {}; + const user = {}; + const model = { + saveToken: sinon.stub().returns(true), + }; + const handler = new grant_types_1.ImplicitGrantType({ + accessTokenLifetime: 120, + model, + user, + }); + sinon.stub(handler, 'validateScope').returns('foobar-scope'); + sinon + .stub(handler, 'generateAccessToken') + .returns(Promise.resolve('foobar-token')); + sinon.stub(handler, 'getAccessTokenExpiresAt').returns('foo-1234'); + return handler + .saveToken(user, client, 'foobar') + .then(() => { + model.saveToken.callCount.should.equal(1); + model.saveToken.firstCall.args.should.have.length(3); + model.saveToken.firstCall.args[0].should.eql({ + accessToken: 'foobar-token', + accessTokenExpiresAt: 'foo-1234', + scope: 'foobar-scope', + }); + model.saveToken.firstCall.args[1].should.equal(client); + model.saveToken.firstCall.args[2].should.equal(user); + model.saveToken.firstCall.thisValue.should.equal(model); + }) + .catch(should.fail); + }); + }); +}); +//# sourceMappingURL=implict-grant-type.spec.js.map \ No newline at end of file diff --git a/dist/test/unit/grant-types/implict-grant-type.spec.js.map b/dist/test/unit/grant-types/implict-grant-type.spec.js.map new file mode 100644 index 000000000..e751647db --- /dev/null +++ b/dist/test/unit/grant-types/implict-grant-type.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"implict-grant-type.spec.js","sourceRoot":"","sources":["../../../../test/unit/grant-types/implict-grant-type.spec.ts"],"names":[],"mappings":";;AAAA,iCAAiC;AACjC,+BAA+B;AAC/B,0DAA6D;AAK7D,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;QAC3B,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;YACzC,MAAM,MAAM,GAAG,EAAE,CAAC;YAClB,MAAM,IAAI,GAAG,EAAE,CAAC;YAChB,MAAM,KAAK,GAAG;gBACZ,SAAS,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;aACtC,CAAC;YACF,MAAM,OAAO,GAAQ,IAAI,+BAAiB,CAAC;gBACzC,mBAAmB,EAAE,GAAG;gBACxB,KAAK;gBACL,IAAI;aACL,CAAC,CAAC;YAEH,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;YAC7D,KAAK;iBACF,IAAI,CAAC,OAAO,EAAE,qBAAqB,CAAC;iBACpC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC;YAC5C,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,yBAAyB,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;YAEnE,OAAO,OAAO;iBACX,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC;iBACjC,IAAI,CAAC,GAAG,EAAE;gBACT,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBAC1C,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;gBACrD,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;oBAC3C,WAAW,EAAE,cAAc;oBAC3B,oBAAoB,EAAE,UAAU;oBAChC,KAAK,EAAE,cAAc;iBACtB,CAAC,CAAC;gBACH,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;gBACvD,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBACrD,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAC1D,CAAC,CAAC;iBACD,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACxB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"} \ No newline at end of file diff --git a/dist/test/unit/grant-types/password-grant-type.spec.d.ts b/dist/test/unit/grant-types/password-grant-type.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/dist/test/unit/grant-types/password-grant-type.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/test/unit/grant-types/password-grant-type.spec.js b/dist/test/unit/grant-types/password-grant-type.spec.js new file mode 100644 index 000000000..b815795e7 --- /dev/null +++ b/dist/test/unit/grant-types/password-grant-type.spec.js @@ -0,0 +1,75 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const should = require("should"); +const sinon = require("sinon"); +const grant_types_1 = require("../../../lib/grant-types"); +const request_1 = require("../../../lib/request"); +describe('PasswordGrantType', () => { + describe('getUser()', () => { + it('should call `model.getUser()`', async () => { + const model = { + getUser: sinon.stub().returns(true), + saveToken() { }, + }; + const handler = new grant_types_1.PasswordGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { username: 'foo', password: 'bar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await handler.getUser(request); + model.getUser.callCount.should.equal(1); + model.getUser.firstCall.args.should.have.length(2); + model.getUser.firstCall.args[0].should.equal('foo'); + model.getUser.firstCall.args[1].should.equal('bar'); + model.getUser.firstCall.thisValue.should.equal(model); + } + catch (error) { + should.fail('should.fail', ''); + } + }); + }); + describe('saveToken()', () => { + it('should call `model.saveToken()`', async () => { + const client = {}; + const user = {}; + const model = { + getUser() { }, + saveToken: sinon.stub().returns(true), + }; + const handler = new grant_types_1.PasswordGrantType({ + accessTokenLifetime: 120, + model, + }); + sinon.stub(handler, 'validateScope').returns('foobar'); + sinon.stub(handler, 'generateAccessToken').returns('foo'); + sinon.stub(handler, 'generateRefreshToken').returns('bar'); + sinon.stub(handler, 'getAccessTokenExpiresAt').returns('biz'); + sinon.stub(handler, 'getRefreshTokenExpiresAt').returns('baz'); + try { + await handler.saveToken(user, client, 'foobar'); + model.saveToken.callCount.should.equal(1); + model.saveToken.firstCall.args.should.have.length(3); + model.saveToken.firstCall.args[0].should.eql({ + accessToken: 'foo', + accessTokenExpiresAt: 'biz', + refreshToken: 'bar', + refreshTokenExpiresAt: 'baz', + scope: 'foobar', + }); + model.saveToken.firstCall.args[1].should.equal(client); + model.saveToken.firstCall.args[2].should.equal(user); + model.saveToken.firstCall.thisValue.should.equal(model); + } + catch (error) { + should.fail('should.fail', ''); + } + }); + }); +}); +//# sourceMappingURL=password-grant-type.spec.js.map \ No newline at end of file diff --git a/dist/test/unit/grant-types/password-grant-type.spec.js.map b/dist/test/unit/grant-types/password-grant-type.spec.js.map new file mode 100644 index 000000000..105615456 --- /dev/null +++ b/dist/test/unit/grant-types/password-grant-type.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"password-grant-type.spec.js","sourceRoot":"","sources":["../../../../test/unit/grant-types/password-grant-type.spec.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/test/unit/grant-types/refresh-token-grant-type.spec.d.ts b/dist/test/unit/grant-types/refresh-token-grant-type.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/dist/test/unit/grant-types/refresh-token-grant-type.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/test/unit/grant-types/refresh-token-grant-type.spec.js b/dist/test/unit/grant-types/refresh-token-grant-type.spec.js new file mode 100644 index 000000000..059c7e6c4 --- /dev/null +++ b/dist/test/unit/grant-types/refresh-token-grant-type.spec.js @@ -0,0 +1,278 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const should = require("should"); +const sinon = require("sinon"); +const grant_types_1 = require("../../../lib/grant-types"); +const request_1 = require("../../../lib/request"); +describe('RefreshTokenGrantType', () => { + describe('handle()', () => { + it('should revoke the previous token', () => { + const token = { accessToken: 'foo', client: {}, user: {} }; + const model = { + getRefreshToken() { + return token; + }, + saveToken() { + return { accessToken: 'bar', client: {}, user: {} }; + }, + revokeToken: sinon.stub().returns({ + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date().getTime() / 2), + user: {}, + }), + }; + const handler = new grant_types_1.RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { refresh_token: 'bar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + const client = {}; + return handler + .handle(request, client) + .then(() => { + model.revokeToken.callCount.should.equal(1); + model.revokeToken.firstCall.args.should.have.length(1); + model.revokeToken.firstCall.args[0].should.equal(token); + model.revokeToken.firstCall.thisValue.should.equal(model); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + describe('getRefreshToken()', () => { + it('should call `model.getRefreshToken()`', () => { + const model = { + getRefreshToken: sinon + .stub() + .returns({ accessToken: 'foo', client: {}, user: {} }), + saveToken() { }, + revokeToken() { }, + }; + const handler = new grant_types_1.RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { refresh_token: 'bar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + const client = {}; + return handler + .getRefreshToken(request, client) + .then(() => { + model.getRefreshToken.callCount.should.equal(1); + model.getRefreshToken.firstCall.args.should.have.length(1); + model.getRefreshToken.firstCall.args[0].should.equal('bar'); + model.getRefreshToken.firstCall.thisValue.should.equal(model); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + describe('revokeToken()', () => { + it('should call `model.revokeToken()`', () => { + const model = { + getRefreshToken() { }, + revokeToken: sinon.stub().returns({ + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date().getTime() / 2), + user: {}, + }), + saveToken() { }, + }; + const handler = new grant_types_1.RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const token = {}; + return handler + .revokeToken(token) + .then(() => { + model.revokeToken.callCount.should.equal(1); + model.revokeToken.firstCall.args.should.have.length(1); + model.revokeToken.firstCall.args[0].should.equal(token); + model.revokeToken.firstCall.thisValue.should.equal(model); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + it('should not call `model.revokeToken()`', () => { + const model = { + getRefreshToken() { }, + revokeToken: sinon.stub().returns({ + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date().getTime() / 2), + user: {}, + }), + saveToken() { }, + }; + const handler = new grant_types_1.RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + alwaysIssueNewRefreshToken: false, + }); + const token = {}; + return handler + .revokeToken(token) + .then(() => { + model.revokeToken.callCount.should.equal(0); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + it('should not call `model.revokeToken()`', () => { + const model = { + getRefreshToken() { }, + revokeToken: sinon.stub().returns({ + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date().getTime() / 2), + user: {}, + }), + saveToken() { }, + }; + const handler = new grant_types_1.RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + alwaysIssueNewRefreshToken: true, + }); + const token = {}; + return handler + .revokeToken(token) + .then(() => { + model.revokeToken.callCount.should.equal(1); + model.revokeToken.firstCall.args.should.have.length(1); + model.revokeToken.firstCall.args[0].should.equal(token); + model.revokeToken.firstCall.thisValue.should.equal(model); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + describe('saveToken()', () => { + it('should call `model.saveToken()`', () => { + const client = {}; + const user = {}; + const model = { + getRefreshToken() { }, + revokeToken() { }, + saveToken: sinon.stub().returns(true), + }; + const handler = new grant_types_1.RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + sinon.stub(handler, 'generateAccessToken').returns('foo'); + sinon.stub(handler, 'generateRefreshToken').returns('bar'); + sinon.stub(handler, 'getAccessTokenExpiresAt').returns('biz'); + sinon.stub(handler, 'getRefreshTokenExpiresAt').returns('baz'); + return handler + .saveToken(user, client, 'foobar') + .then(() => { + model.saveToken.callCount.should.equal(1); + model.saveToken.firstCall.args.should.have.length(3); + model.saveToken.firstCall.args[0].should.eql({ + accessToken: 'foo', + accessTokenExpiresAt: 'biz', + refreshToken: 'bar', + refreshTokenExpiresAt: 'baz', + scope: 'foobar', + }); + model.saveToken.firstCall.args[1].should.equal(client); + model.saveToken.firstCall.args[2].should.equal(user); + model.saveToken.firstCall.thisValue.should.equal(model); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + it('should call `model.saveToken()` without refresh token', () => { + const client = {}; + const user = {}; + const model = { + getRefreshToken() { }, + revokeToken() { }, + saveToken: sinon.stub().returns(true), + }; + const handler = new grant_types_1.RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + alwaysIssueNewRefreshToken: false, + }); + sinon.stub(handler, 'generateAccessToken').returns('foo'); + sinon.stub(handler, 'generateRefreshToken').returns('bar'); + sinon.stub(handler, 'getAccessTokenExpiresAt').returns('biz'); + sinon.stub(handler, 'getRefreshTokenExpiresAt').returns('baz'); + return handler + .saveToken(user, client, 'foobar') + .then(() => { + model.saveToken.callCount.should.equal(1); + model.saveToken.firstCall.args.should.have.length(3); + model.saveToken.firstCall.args[0].should.eql({ + accessToken: 'foo', + accessTokenExpiresAt: 'biz', + scope: 'foobar', + }); + model.saveToken.firstCall.args[1].should.equal(client); + model.saveToken.firstCall.args[2].should.equal(user); + model.saveToken.firstCall.thisValue.should.equal(model); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + it('should call `model.saveToken()` with refresh token', () => { + const client = {}; + const user = {}; + const model = { + getRefreshToken() { }, + revokeToken() { }, + saveToken: sinon.stub().returns(true), + }; + const handler = new grant_types_1.RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + alwaysIssueNewRefreshToken: true, + }); + sinon.stub(handler, 'generateAccessToken').returns('foo'); + sinon.stub(handler, 'generateRefreshToken').returns('bar'); + sinon.stub(handler, 'getAccessTokenExpiresAt').returns('biz'); + sinon.stub(handler, 'getRefreshTokenExpiresAt').returns('baz'); + return handler + .saveToken(user, client, 'foobar') + .then(() => { + model.saveToken.callCount.should.equal(1); + model.saveToken.firstCall.args.should.have.length(3); + model.saveToken.firstCall.args[0].should.eql({ + accessToken: 'foo', + accessTokenExpiresAt: 'biz', + refreshToken: 'bar', + refreshTokenExpiresAt: 'baz', + scope: 'foobar', + }); + model.saveToken.firstCall.args[1].should.equal(client); + model.saveToken.firstCall.args[2].should.equal(user); + model.saveToken.firstCall.thisValue.should.equal(model); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); +}); +//# sourceMappingURL=refresh-token-grant-type.spec.js.map \ No newline at end of file diff --git a/dist/test/unit/grant-types/refresh-token-grant-type.spec.js.map b/dist/test/unit/grant-types/refresh-token-grant-type.spec.js.map new file mode 100644 index 000000000..ee008af6a --- /dev/null +++ b/dist/test/unit/grant-types/refresh-token-grant-type.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"refresh-token-grant-type.spec.js","sourceRoot":"","sources":["../../../../test/unit/grant-types/refresh-token-grant-type.spec.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/test/unit/handlers/authenticate-handler.spec.d.ts b/dist/test/unit/handlers/authenticate-handler.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/dist/test/unit/handlers/authenticate-handler.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/test/unit/handlers/authenticate-handler.spec.js b/dist/test/unit/handlers/authenticate-handler.spec.js new file mode 100644 index 000000000..f6498a4d1 --- /dev/null +++ b/dist/test/unit/handlers/authenticate-handler.spec.js @@ -0,0 +1,144 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const should = require("should"); +const sinon = require("sinon"); +const errors_1 = require("../../../lib/errors"); +const handlers_1 = require("../../../lib/handlers"); +const request_1 = require("../../../lib/request"); +describe('AuthenticateHandler', () => { + describe('getTokenFromRequest()', () => { + describe('with bearer token in the request authorization header', () => { + it('should call `getTokenFromRequestHeader()`', () => { + const handler = new handlers_1.AuthenticateHandler({ + model: { getAccessToken() { } }, + }); + const request = new request_1.Request({ + body: {}, + headers: { Authorization: 'Bearer foo' }, + method: 'ANY', + query: {}, + }); + sinon.stub(handler, 'getTokenFromRequestHeader'); + handler.getTokenFromRequest(request); + handler.getTokenFromRequestHeader.callCount.should.equal(1); + handler.getTokenFromRequestHeader.firstCall.args[0].should.equal(request); + handler.getTokenFromRequestHeader.restore(); + }); + }); + describe('with bearer token in the request query', () => { + it('should call `getTokenFromRequestQuery()`', () => { + const handler = new handlers_1.AuthenticateHandler({ + model: { getAccessToken() { } }, + }); + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'ANY', + query: { access_token: 'foo' }, + }); + sinon.stub(handler, 'getTokenFromRequestQuery'); + handler.getTokenFromRequest(request); + handler.getTokenFromRequestQuery.callCount.should.equal(1); + handler.getTokenFromRequestQuery.firstCall.args[0].should.equal(request); + handler.getTokenFromRequestQuery.restore(); + }); + }); + describe('with bearer token in the request body', () => { + it('should call `getTokenFromRequestBody()`', () => { + const handler = new handlers_1.AuthenticateHandler({ + model: { getAccessToken() { } }, + }); + const request = new request_1.Request({ + body: { access_token: 'foo' }, + headers: {}, + method: 'ANY', + query: {}, + }); + sinon.stub(handler, 'getTokenFromRequestBody'); + handler.getTokenFromRequest(request); + handler.getTokenFromRequestBody.callCount.should.equal(1); + handler.getTokenFromRequestBody.firstCall.args[0].should.equal(request); + handler.getTokenFromRequestBody.restore(); + }); + }); + }); + describe('getAccessToken()', () => { + it('should call `model.getAccessToken()`', () => { + const model = { + getAccessToken: sinon.stub().returns({ user: {} }), + }; + const handler = new handlers_1.AuthenticateHandler({ model }); + return handler + .getAccessToken('foo') + .then(() => { + model.getAccessToken.callCount.should.equal(1); + model.getAccessToken.firstCall.args.should.have.length(1); + model.getAccessToken.firstCall.args[0].should.equal('foo'); + model.getAccessToken.firstCall.thisValue.should.equal(model); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + describe('validateAccessToken()', () => { + it('should fail if token has no valid `accessTokenExpiresAt` date', () => { + const model = { + getAccessToken() { }, + }; + const handler = new handlers_1.AuthenticateHandler({ model }); + let failed = false; + try { + handler.validateAccessToken({ + user: {}, + }); + } + catch (err) { + err.should.be.an.instanceOf(errors_1.ServerError); + failed = true; + } + failed.should.equal(true); + }); + it('should succeed if token has valid `accessTokenExpiresAt` date', () => { + const model = { + getAccessToken() { }, + }; + const handler = new handlers_1.AuthenticateHandler({ model }); + try { + handler.validateAccessToken({ + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }); + } + catch (err) { + should.fail('should.fail', ''); + } + }); + }); + describe('verifyScope()', () => { + it('should call `model.getAccessToken()` if scope is defined', () => { + const model = { + getAccessToken() { }, + verifyScope: sinon.stub().returns(true), + }; + const handler = new handlers_1.AuthenticateHandler({ + addAcceptedScopesHeader: true, + addAuthorizedScopesHeader: true, + model, + scope: 'bar', + }); + return handler + .verifyScope('foo') + .then(() => { + model.verifyScope.callCount.should.equal(1); + model.verifyScope.firstCall.args.should.have.length(2); + model.verifyScope.firstCall.args[0].should.equal('foo', 'bar'); + model.verifyScope.firstCall.thisValue.should.equal(model); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); +}); +//# sourceMappingURL=authenticate-handler.spec.js.map \ No newline at end of file diff --git a/dist/test/unit/handlers/authenticate-handler.spec.js.map b/dist/test/unit/handlers/authenticate-handler.spec.js.map new file mode 100644 index 000000000..b442312e4 --- /dev/null +++ b/dist/test/unit/handlers/authenticate-handler.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"authenticate-handler.spec.js","sourceRoot":"","sources":["../../../../test/unit/handlers/authenticate-handler.spec.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/test/unit/handlers/authorize-handler.spec.d.ts b/dist/test/unit/handlers/authorize-handler.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/dist/test/unit/handlers/authorize-handler.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/test/unit/handlers/authorize-handler.spec.js b/dist/test/unit/handlers/authorize-handler.spec.js new file mode 100644 index 000000000..6269b609f --- /dev/null +++ b/dist/test/unit/handlers/authorize-handler.spec.js @@ -0,0 +1,76 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const should = require("should"); +const sinon = require("sinon"); +const handlers_1 = require("../../../lib/handlers"); +const request_1 = require("../../../lib/request"); +const response_1 = require("../../../lib/response"); +describe('AuthorizeHandler', () => { + describe('getClient()', () => { + it('should call `model.getClient()`', async () => { + const model = { + getAccessToken() { }, + getClient: sinon.stub().returns(Promise.resolve({ + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + })), + saveAuthorizationCode() { }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await handler.getClient(request); + model.getClient.callCount.should.equal(1); + model.getClient.firstCall.args.should.have.length(1); + model.getClient.firstCall.args[0].should.equal(12345); + model.getClient.firstCall.thisValue.should.equal(model); + } + catch (error) { + should.fail('should.fail', ''); + } + }); + }); + describe('getUser()', () => { + it('should call `authenticateHandler.getUser()`', () => { + const authenticateHandler = { + handle: sinon.stub().returns(Promise.resolve({})), + }; + const model = { + getClient() { }, + saveAuthorizationCode() { }, + }; + const handler = new handlers_1.AuthorizeHandler({ + authenticateHandler, + authorizationCodeLifetime: 120, + model, + }); + const request = new request_1.Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + const response = new response_1.Response(); + return handler + .getUser(request, response) + .then(() => { + authenticateHandler.handle.callCount.should.equal(1); + authenticateHandler.handle.firstCall.args.should.have.length(2); + authenticateHandler.handle.firstCall.args[0].should.equal(request); + authenticateHandler.handle.firstCall.args[1].should.equal(response); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); +}); +//# sourceMappingURL=authorize-handler.spec.js.map \ No newline at end of file diff --git a/dist/test/unit/handlers/authorize-handler.spec.js.map b/dist/test/unit/handlers/authorize-handler.spec.js.map new file mode 100644 index 000000000..29e4cad55 --- /dev/null +++ b/dist/test/unit/handlers/authorize-handler.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"authorize-handler.spec.js","sourceRoot":"","sources":["../../../../test/unit/handlers/authorize-handler.spec.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/test/unit/handlers/revoke-handlers.spec.d.ts b/dist/test/unit/handlers/revoke-handlers.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/dist/test/unit/handlers/revoke-handlers.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/test/unit/handlers/revoke-handlers.spec.js b/dist/test/unit/handlers/revoke-handlers.spec.js new file mode 100644 index 000000000..414464646 --- /dev/null +++ b/dist/test/unit/handlers/revoke-handlers.spec.js @@ -0,0 +1,117 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const should = require("should"); +const sinon = require("sinon"); +const handlers_1 = require("../../../lib/handlers"); +const request_1 = require("../../../lib/request"); +describe('RevokeHandler', () => { + describe('handleRevokeToken()', () => { + it('should call `model.getAccessToken()` and `model.getRefreshToken()`', () => { + const model = { + getClient() { }, + revokeToken: sinon.stub().returns(true), + getRefreshToken: sinon.stub().returns({ + refreshToken: 'hash', + client: {}, + refreshTokenExpiresAt: new Date(Date.now() * 2), + user: {}, + }), + getAccessToken: sinon.stub().returns(false), + }; + const handler = new handlers_1.RevokeHandler({ model }); + const request = new request_1.Request({ + body: { token: 'foo' }, + headers: {}, + method: 'ANY', + query: {}, + }); + const client = {}; + return handler + .handleRevokeToken(request, client) + .then(() => { + model.getAccessToken.callCount.should.equal(1); + model.getAccessToken.firstCall.args[0].should.equal('foo'); + model.getRefreshToken.callCount.should.equal(1); + model.getRefreshToken.firstCall.args[0].should.equal('foo'); + }) + .catch(should.fail); + }); + }); + describe('getClient()', () => { + it('should call `model.getClient()`', () => { + const model = { + getClient: sinon.stub().returns({ grants: ['password'] }), + revokeToken() { }, + getRefreshToken() { }, + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + const request = new request_1.Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: 'ANY', + query: {}, + }); + return handler + .getClient(request) + .then(() => { + model.getClient.callCount.should.equal(1); + model.getClient.firstCall.args.should.have.length(2); + model.getClient.firstCall.args[0].should.equal(12345); + model.getClient.firstCall.args[1].should.equal('secret'); + }) + .catch(should.fail); + }); + }); + describe('getRefreshToken()', () => { + it('should call `model.getRefreshToken()`', () => { + const model = { + getClient() { }, + revokeToken() { }, + getAccessToken() { }, + getRefreshToken: sinon.stub().returns({ + refreshToken: 'hash', + client: {}, + refreshTokenExpiresAt: new Date(Date.now() * 2), + user: {}, + }), + }; + const handler = new handlers_1.RevokeHandler({ model }); + const token = 'hash'; + const client = {}; + return handler + .getRefreshToken(token, client) + .then(() => { + model.getRefreshToken.callCount.should.equal(1); + model.getRefreshToken.firstCall.args.should.have.length(1); + model.getRefreshToken.firstCall.args[0].should.equal(token); + }) + .catch(should.fail); + }); + }); + describe('revokeToken()', () => { + it('should call `model.revokeToken()`', () => { + const model = { + getClient() { }, + revokeToken: sinon.stub().returns(true), + getRefreshToken: sinon.stub().returns({ + refreshToken: 'hash', + client: {}, + refreshTokenExpiresAt: new Date(Date.now() * 2), + user: {}, + }), + getAccessToken() { }, + }; + const handler = new handlers_1.RevokeHandler({ model }); + const token = 'hash'; + return handler + .revokeToken(token) + .then(() => { + model.revokeToken.callCount.should.equal(1); + model.revokeToken.firstCall.args.should.have.length(1); + }) + .catch(should.fail); + }); + }); +}); +//# sourceMappingURL=revoke-handlers.spec.js.map \ No newline at end of file diff --git a/dist/test/unit/handlers/revoke-handlers.spec.js.map b/dist/test/unit/handlers/revoke-handlers.spec.js.map new file mode 100644 index 000000000..2b776fae6 --- /dev/null +++ b/dist/test/unit/handlers/revoke-handlers.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"revoke-handlers.spec.js","sourceRoot":"","sources":["../../../../test/unit/handlers/revoke-handlers.spec.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/test/unit/handlers/token-handler.spec.d.ts b/dist/test/unit/handlers/token-handler.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/dist/test/unit/handlers/token-handler.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/test/unit/handlers/token-handler.spec.js b/dist/test/unit/handlers/token-handler.spec.js new file mode 100644 index 000000000..41ad27224 --- /dev/null +++ b/dist/test/unit/handlers/token-handler.spec.js @@ -0,0 +1,42 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const should = require("should"); +const sinon = require("sinon"); +const handlers_1 = require("../../../lib/handlers"); +const request_1 = require("../../../lib/request"); +describe('TokenHandler', () => { + describe('getClient()', () => { + it('should call `model.getClient()`', () => { + const model = { + getClient: sinon + .stub() + .returns(Promise.resolve({ grants: ['password'] })), + saveToken() { }, + }; + const handler = new handlers_1.TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new request_1.Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: 'ANY', + query: {}, + }); + return handler + .getClient(request, {}) + .then(() => { + model.getClient.callCount.should.equal(1); + model.getClient.firstCall.args.should.have.length(2); + model.getClient.firstCall.args[0].should.equal(12345); + model.getClient.firstCall.args[1].should.equal('secret'); + model.getClient.firstCall.thisValue.should.equal(model); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); +}); +//# sourceMappingURL=token-handler.spec.js.map \ No newline at end of file diff --git a/dist/test/unit/handlers/token-handler.spec.js.map b/dist/test/unit/handlers/token-handler.spec.js.map new file mode 100644 index 000000000..6adb7326e --- /dev/null +++ b/dist/test/unit/handlers/token-handler.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"token-handler.spec.js","sourceRoot":"","sources":["../../../../test/unit/handlers/token-handler.spec.ts"],"names":[],"mappings":";;AAAA,iCAAiC;AACjC,+BAA+B;AAC/B,oDAAqD;AACrD,kDAA+C;AAM/C,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;QAC3B,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;YACzC,MAAM,KAAK,GAAG;gBACZ,SAAS,EAAE,KAAK;qBACb,IAAI,EAAE;qBACN,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;gBACrD,SAAS,KAAI,CAAC;aACf,CAAC;YACF,MAAM,OAAO,GAAG,IAAI,uBAAY,CAAC;gBAC/B,mBAAmB,EAAE,GAAG;gBACxB,KAAK;gBACL,oBAAoB,EAAE,GAAG;aAC1B,CAAC,CAAC;YACH,MAAM,OAAO,GAAG,IAAI,iBAAO,CAAC;gBAC1B,IAAI,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE;gBACnD,OAAO,EAAE,EAAE;gBACX,MAAM,EAAE,KAAK;gBACb,KAAK,EAAE,EAAE;aACV,CAAC,CAAC;YAEH,OAAO,OAAO;iBACX,SAAS,CAAC,OAAO,EAAE,EAAE,CAAC;iBACtB,IAAI,CAAC,GAAG,EAAE;gBACT,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBAC1C,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;gBACrD,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;gBACtD,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;gBACzD,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAC1D,CAAC,CAAC;iBACD,KAAK,CAAC,GAAG,EAAE;gBACV,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;YACjC,CAAC,CAAC,CAAC;QACP,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"} \ No newline at end of file diff --git a/dist/test/unit/models/token-model.spec.d.ts b/dist/test/unit/models/token-model.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/dist/test/unit/models/token-model.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/test/unit/models/token-model.spec.js b/dist/test/unit/models/token-model.spec.js new file mode 100644 index 000000000..836a14476 --- /dev/null +++ b/dist/test/unit/models/token-model.spec.js @@ -0,0 +1,21 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const models_1 = require("../../../lib/models"); +describe('Model', () => { + describe('constructor()', () => { + it('should calculate `accessTokenLifetime` if `accessTokenExpiresAt` is set', () => { + const atExpiresAt = new Date(); + atExpiresAt.setHours(new Date().getHours() + 1); + const data = { + accessToken: 'foo', + client: 'bar', + user: 'tar', + accessTokenExpiresAt: atExpiresAt, + }; + const model = new models_1.TokenModel(data); + model.accessTokenLifetime.should.be.Number(); + model.accessTokenLifetime.should.be.approximately(3600, 2); + }); + }); +}); +//# sourceMappingURL=token-model.spec.js.map \ No newline at end of file diff --git a/dist/test/unit/models/token-model.spec.js.map b/dist/test/unit/models/token-model.spec.js.map new file mode 100644 index 000000000..6006eb95a --- /dev/null +++ b/dist/test/unit/models/token-model.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"token-model.spec.js","sourceRoot":"","sources":["../../../../test/unit/models/token-model.spec.ts"],"names":[],"mappings":";;AAAA,gDAAiD;AAMjD,QAAQ,CAAC,OAAO,EAAE,GAAG,EAAE;IACrB,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;QAC7B,EAAE,CAAC,yEAAyE,EAAE,GAAG,EAAE;YACjF,MAAM,WAAW,GAAG,IAAI,IAAI,EAAE,CAAC;YAC/B,WAAW,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;YAEhD,MAAM,IAAI,GAAG;gBACX,WAAW,EAAE,KAAK;gBAClB,MAAM,EAAE,KAAK;gBACb,IAAI,EAAE,KAAK;gBACX,oBAAoB,EAAE,WAAW;aAClC,CAAC;YAEF,MAAM,KAAK,GAAG,IAAI,mBAAU,CAAC,IAAI,CAAC,CAAC;YACnC,KAAK,CAAC,mBAAmB,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC;YAC7C,KAAK,CAAC,mBAAmB,CAAC,MAAM,CAAC,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QAC7D,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"} \ No newline at end of file diff --git a/dist/test/unit/request.spec.d.ts b/dist/test/unit/request.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/dist/test/unit/request.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/test/unit/request.spec.js b/dist/test/unit/request.spec.js new file mode 100644 index 000000000..3799e0d5b --- /dev/null +++ b/dist/test/unit/request.spec.js @@ -0,0 +1,136 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const should = require("should"); +const request_1 = require("../../lib/request"); +function generateBaseRequest() { + return { + query: { + foo: 'bar', + }, + method: 'GET', + headers: { + bar: 'foo', + }, + body: { + foobar: 'barfoo', + }, + }; +} +describe('Request', () => { + it('should instantiate with a basic request', () => { + const originalRequest = generateBaseRequest(); + const request = new request_1.Request(originalRequest); + request.headers.should.eql(originalRequest.headers); + request.method.should.eql(originalRequest.method); + request.query.should.eql(originalRequest.query); + request.body.should.eql(originalRequest.body); + }); + it('should allow a request to be passed without a body', () => { + const originalRequest = generateBaseRequest(); + delete originalRequest.body; + const request = new request_1.Request(originalRequest); + request.headers.should.eql(originalRequest.headers); + request.method.should.eql(originalRequest.method); + request.query.should.eql(originalRequest.query); + request.body.should.eql({}); + }); + it('should throw if headers are not passed to the constructor', () => { + const originalRequest = generateBaseRequest(); + delete originalRequest.headers; + (() => { + new request_1.Request(originalRequest); + }).should.throw('Missing parameter: `headers`'); + }); + it('should throw if query string isnt passed to the constructor', () => { + const originalRequest = generateBaseRequest(); + delete originalRequest.query; + (() => { + new request_1.Request(originalRequest); + }).should.throw('Missing parameter: `query`'); + }); + it('should throw if method isnt passed to the constructor', () => { + const originalRequest = generateBaseRequest(); + delete originalRequest.method; + (() => { + new request_1.Request(originalRequest); + }).should.throw('Missing parameter: `method`'); + }); + it('should convert all header keys to lowercase', () => { + const originalRequest = generateBaseRequest(); + originalRequest.headers = { + Foo: 'bar', + BAR: 'foo', + }; + const request = new request_1.Request(originalRequest); + request.headers.foo.should.eql('bar'); + request.headers.bar.should.eql('foo'); + should.not.exist(request.headers.Foo); + should.not.exist(request.headers.BAR); + }); + it('should include additional properties passed in the request', () => { + const originalRequest = generateBaseRequest(); + originalRequest.custom = { + newFoo: 'newBar', + }; + originalRequest.custom2 = { + newBar: 'newFoo', + }; + const request = new request_1.Request(originalRequest); + request.headers.should.eql(originalRequest.headers); + request.method.should.eql(originalRequest.method); + request.query.should.eql(originalRequest.query); + request.body.should.eql(originalRequest.body); + request.custom.should.eql(originalRequest.custom); + request.custom2.should.eql(originalRequest.custom2); + }); + it('should include additional properties passed in the request', () => { + const originalRequest = generateBaseRequest(); + originalRequest.custom = { + newFoo: 'newBar', + }; + originalRequest.custom2 = { + newBar: 'newFoo', + }; + const request = new request_1.Request(originalRequest); + request.headers.should.eql(originalRequest.headers); + request.method.should.eql(originalRequest.method); + request.query.should.eql(originalRequest.query); + request.body.should.eql(originalRequest.body); + request.custom.should.eql(originalRequest.custom); + request.custom2.should.eql(originalRequest.custom2); + }); + it('should allow getting of headers using `request.get`', () => { + const originalRequest = generateBaseRequest(); + const request = new request_1.Request(originalRequest); + request.get('bar').should.eql(originalRequest.headers.bar); + }); + it('should allow getting of headers using `request.get`', () => { + const originalRequest = generateBaseRequest(); + const request = new request_1.Request(originalRequest); + request.get('bar').should.eql(originalRequest.headers.bar); + }); + it('should allow getting of headers using `request.get`', () => { + const originalRequest = generateBaseRequest(); + const request = new request_1.Request(originalRequest); + request.get('bar').should.eql(originalRequest.headers.bar); + }); + it('should validate the content-type', () => { + const originalRequest = generateBaseRequest(); + originalRequest.headers['content-type'] = + 'application/x-www-form-urlencoded'; + originalRequest.headers['content-length'] = JSON.stringify(originalRequest.body).length; + const request = new request_1.Request(originalRequest); + request + .is('application/x-www-form-urlencoded') + .should.eql('application/x-www-form-urlencoded'); + }); + it('should return false if the content-type is invalid', () => { + const originalRequest = generateBaseRequest(); + originalRequest.headers['content-type'] = + 'application/x-www-form-urlencoded'; + originalRequest.headers['content-length'] = JSON.stringify(originalRequest.body).length; + const request = new request_1.Request(originalRequest); + request.is('application/json').should.be.false(); + }); +}); +//# sourceMappingURL=request.spec.js.map \ No newline at end of file diff --git a/dist/test/unit/request.spec.js.map b/dist/test/unit/request.spec.js.map new file mode 100644 index 000000000..8db7b2b83 --- /dev/null +++ b/dist/test/unit/request.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"request.spec.js","sourceRoot":"","sources":["../../../test/unit/request.spec.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/test/unit/response.spec.d.ts b/dist/test/unit/response.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/dist/test/unit/response.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/test/unit/response.spec.js b/dist/test/unit/response.spec.js new file mode 100644 index 000000000..a8212fa67 --- /dev/null +++ b/dist/test/unit/response.spec.js @@ -0,0 +1,93 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const should = require("should"); +const response_1 = require("../../lib/response"); +const generateBaseResponse = () => { + return { + headers: { + bar: 'foo', + }, + body: { + foobar: 'barfoo', + }, + }; +}; +describe('Response', () => { + it('should instantiate with a basic request', () => { + const originalResponse = generateBaseResponse(); + const response = new response_1.Response(originalResponse); + response.headers.should.eql(originalResponse.headers); + response.body.should.eql(originalResponse.body); + response.status.should.eql(200); + }); + it('should allow a response to be passed without a body', () => { + const originalResponse = generateBaseResponse(); + delete originalResponse.body; + const response = new response_1.Response(originalResponse); + response.headers.should.eql(originalResponse.headers); + response.body.should.eql({}); + response.status.should.eql(200); + }); + it('should allow a response to be passed without headers', () => { + const originalResponse = generateBaseResponse(); + delete originalResponse.headers; + const response = new response_1.Response(originalResponse); + response.headers.should.eql({}); + response.body.should.eql(originalResponse.body); + response.status.should.eql(200); + }); + it('should convert all header keys to lowercase', () => { + const originalResponse = generateBaseResponse(); + originalResponse.headers = { + Foo: 'bar', + BAR: 'foo', + }; + const response = new response_1.Response(originalResponse); + response.headers.foo.should.eql('bar'); + response.headers.bar.should.eql('foo'); + should.not.exist(response.headers.Foo); + should.not.exist(response.headers.BAR); + }); + it('should include additional properties passed in the response', () => { + const originalResponse = generateBaseResponse(); + originalResponse.custom = { + newFoo: 'newBar', + }; + originalResponse.custom2 = { + newBar: 'newFoo', + }; + const response = new response_1.Response(originalResponse); + response.headers.should.eql(originalResponse.headers); + response.body.should.eql(originalResponse.body); + response.custom.should.eql(originalResponse.custom); + response.custom2.should.eql(originalResponse.custom2); + }); + it('should allow getting of headers using `response.get`', () => { + const originalResponse = generateBaseResponse(); + const response = new response_1.Response(originalResponse); + response.get('bar').should.eql(originalResponse.headers.bar); + }); + it('should allow getting of headers using `response.get`', () => { + const originalResponse = generateBaseResponse(); + const response = new response_1.Response(originalResponse); + response.get('bar').should.eql(originalResponse.headers.bar); + }); + it('should allow setting of headers using `response.set`', () => { + const originalResponse = generateBaseResponse(); + const response = new response_1.Response(originalResponse); + response.headers.should.eql(originalResponse.headers); + response.set('new_header', 'new_value'); + response.headers.bar.should.eql('foo'); + response.headers.new_header.should.eql('new_value'); + }); + it('should process redirect', () => { + const originalResponse = generateBaseResponse(); + const response = new response_1.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); + }); +}); +//# sourceMappingURL=response.spec.js.map \ No newline at end of file diff --git a/dist/test/unit/response.spec.js.map b/dist/test/unit/response.spec.js.map new file mode 100644 index 000000000..ffd6e7296 --- /dev/null +++ b/dist/test/unit/response.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"response.spec.js","sourceRoot":"","sources":["../../../test/unit/response.spec.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/test/unit/server.spec.d.ts b/dist/test/unit/server.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/dist/test/unit/server.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/test/unit/server.spec.js b/dist/test/unit/server.spec.js new file mode 100644 index 000000000..9d44c2802 --- /dev/null +++ b/dist/test/unit/server.spec.js @@ -0,0 +1,67 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const sinon = require("sinon"); +const handlers_1 = require("../../lib/handlers"); +const server_1 = require("../../lib/server"); +const Authenticate = handlers_1.AuthenticateHandler; +const Authorize = handlers_1.AuthorizeHandler; +const Token = handlers_1.TokenHandler; +describe('Server', () => { + describe('authenticate()', () => { + it('should call `handle`', async () => { + const model = { + getAccessToken() { }, + }; + const server = new server_1.OAuth2Server({ model }); + sinon.stub(Authenticate.prototype, 'handle').returns(Promise.resolve()); + await server.authenticate('foo'); + Authenticate.prototype.handle.callCount.should.equal(1); + Authenticate.prototype.handle.firstCall.args[0].should.equal('foo'); + Authenticate.prototype.handle.restore(); + }); + it('should map string passed as `options` to `options.scope`', async () => { + const model = { + getAccessToken() { }, + verifyScope() { }, + }; + const server = new server_1.OAuth2Server({ model }); + sinon.stub(Authenticate.prototype, 'handle').returns(Promise.resolve()); + await server.authenticate('foo', 'bar', 'test'); + Authenticate.prototype.handle.callCount.should.equal(1); + Authenticate.prototype.handle.firstCall.args[0].should.equal('foo'); + Authenticate.prototype.handle.firstCall.args[1].should.equal('bar'); + Authenticate.prototype.handle.firstCall.thisValue.should.have.property('scope', 'test'); + Authenticate.prototype.handle.restore(); + }); + }); + describe('authorize()', () => { + it('should call `handle`', async () => { + const model = { + getAccessToken() { }, + getClient() { }, + saveAuthorizationCode() { }, + }; + const server = new server_1.OAuth2Server({ model }); + sinon.stub(Authorize.prototype, 'handle').returns(Promise.resolve()); + await server.authorize('foo', 'bar'); + Authorize.prototype.handle.callCount.should.equal(1); + Authorize.prototype.handle.firstCall.args[0].should.equal('foo'); + Authorize.prototype.handle.restore(); + }); + }); + describe('token()', () => { + it('should call `handle`', async () => { + const model = { + getClient() { }, + saveToken() { }, + }; + const server = new server_1.OAuth2Server({ model }); + sinon.stub(Token.prototype, 'handle').returns(Promise.resolve()); + await server.token('foo', 'bar'); + Token.prototype.handle.callCount.should.equal(1); + Token.prototype.handle.firstCall.args[0].should.equal('foo'); + Token.prototype.handle.restore(); + }); + }); +}); +//# sourceMappingURL=server.spec.js.map \ No newline at end of file diff --git a/dist/test/unit/server.spec.js.map b/dist/test/unit/server.spec.js.map new file mode 100644 index 000000000..81044a392 --- /dev/null +++ b/dist/test/unit/server.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"server.spec.js","sourceRoot":"","sources":["../../../test/unit/server.spec.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/docs/api/oauth2-server.rst b/docs/api/oauth2-server.rst index 48acf538a..dcc5a4c3e 100644 --- a/docs/api/oauth2-server.rst +++ b/docs/api/oauth2-server.rst @@ -128,25 +128,27 @@ Authorizes a token request. **Arguments:** -+-----------------------------------------+-----------------+-----------------------------------------------------------------------------+ -| Name | Type | Description | -+=========================================+=================+=============================================================================+ -| request | :doc:`request` | Request object. | -+-----------------------------------------+-----------------+-----------------------------------------------------------------------------+ -| [request.query.allowed=undefined] | String | ``'false'`` to deny the authorization request (see remarks section). | -+-----------------------------------------+-----------------+-----------------------------------------------------------------------------+ -| response | :doc:`response` | Response object. | -+-----------------------------------------+-----------------+-----------------------------------------------------------------------------+ -| [options={}] | Object | Handler options. | -+-----------------------------------------+-----------------+-----------------------------------------------------------------------------+ -| [options.authenticateHandler=undefined] | Object | The authenticate handler (see remarks section). | -+-----------------------------------------+-----------------+-----------------------------------------------------------------------------+ -| [options.allowEmptyState=false] | Boolean | Allow clients to specify an empty ``state``. | -+-----------------------------------------+-----------------+-----------------------------------------------------------------------------+ -| [options.authorizationCodeLifetime=300] | Number | Lifetime of generated authorization codes in seconds (default = 5 minutes). | -+-----------------------------------------+-----------------+-----------------------------------------------------------------------------+ -| [callback=undefined] | Function | Node-style callback to be used instead of the returned ``Promise``. | -+-----------------------------------------+-----------------+-----------------------------------------------------------------------------+ ++-----------------------------------------+-----------------+--------------------------------------------------------------------------------+ +| Name | Type | Description | ++=========================================+=================+================================================================================+ +| request | :doc:`request` | Request object. | ++-----------------------------------------+-----------------+--------------------------------------------------------------------------------+ +| [request.query.allowed=undefined] | String | ``'false'`` to deny the authorization request (see remarks section). | ++-----------------------------------------+-----------------+--------------------------------------------------------------------------------+ +| response | :doc:`response` | Response object. | ++-----------------------------------------+-----------------+--------------------------------------------------------------------------------+ +| [options={}] | Object | Handler options. | ++-----------------------------------------+-----------------+--------------------------------------------------------------------------------+ +| [options.authenticateHandler=undefined] | Object | The authenticate handler (see remarks section). | ++-----------------------------------------+-----------------+--------------------------------------------------------------------------------+ +| [options.allowEmptyState=false] | Boolean | Allow clients to specify an empty ``state``. | ++-----------------------------------------+-----------------+--------------------------------------------------------------------------------+ +| [options.authorizationCodeLifetime=300] | Number | Lifetime of generated authorization codes in seconds (default = 5 minutes). | ++-----------------------------------------+-----------------+--------------------------------------------------------------------------------+ +| [options.accessTokenLifetime=3600] | Number | Lifetime of generated implicit grant access token in seconds (default = 1 hr). | ++-----------------------------------------+-----------------+--------------------------------------------------------------------------------+ +| [callback=undefined] | Function | Node-style callback to be used instead of the returned ``Promise``. | ++-----------------------------------------+-----------------+--------------------------------------------------------------------------------+ **Return value:** diff --git a/docs/model/overview.rst b/docs/model/overview.rst index 5e345abd0..f4363a560 100644 --- a/docs/model/overview.rst +++ b/docs/model/overview.rst @@ -58,6 +58,23 @@ Model functions used by the client credentials grant: - :ref:`Model#getUserFromClient` - :ref:`Model#saveToken` - :ref:`Model#validateScope` +-------- + +.. _ImplicitGrant: + +Implicit Grant +------------------------ + +See :rfc:`Section 4.2 of RFC 6749 <6749#section-4.2>`. + +An implicit grant is used to obtain access tokens optimised for public clients known to operate a particular redirection URI. Usually used for browser-based clients implemented in JavaScript. + +Model functions used by the implicit grant: + +- :ref:`Model#generateAccessToken` +- :ref:`Model#getClient` +- :ref:`Model#saveToken` +- :ref:`Model#validateScope` -------- diff --git a/docs/model/spec.rst b/docs/model/spec.rst index 4cdd1fd11..674f389a5 100644 --- a/docs/model/spec.rst +++ b/docs/model/spec.rst @@ -399,6 +399,7 @@ This model function is **required** for all grant types. - ``authorization_code`` grant - ``client_credentials`` grant +- ``implicit`` grant - ``refresh_token`` grant - ``password`` grant @@ -553,6 +554,7 @@ This model function is **required** for all grant types. - ``authorization_code`` grant - ``client_credentials`` grant +- ``implicit`` grant - ``refresh_token`` grant - ``password`` grant @@ -865,6 +867,7 @@ This model function is **optional**. If not implemented, any scope is accepted. - ``authorization_code`` grant - ``client_credentials`` grant +- ``implicit`` grant - ``password`` grant **Arguments:** diff --git a/index.js b/index.js deleted file mode 100644 index f4f940ab5..000000000 --- a/index.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict'; - -/** - * Expose server and request/response classes. - */ - -exports = module.exports = require('./lib/server'); -exports.Request = require('./lib/request'); -exports.Response = require('./lib/response'); - -/** - * Export helpers for extension grants. - */ - -exports.AbstractGrantType = require('./lib/grant-types/abstract-grant-type'); - -/** - * Export error classes. - */ - -exports.AccessDeniedError = require('./lib/errors/access-denied-error'); -exports.InsufficientScopeError = require('./lib/errors/insufficient-scope-error'); -exports.InvalidArgumentError = require('./lib/errors/invalid-argument-error'); -exports.InvalidClientError = require('./lib/errors/invalid-client-error'); -exports.InvalidGrantError = require('./lib/errors/invalid-grant-error'); -exports.InvalidRequestError = require('./lib/errors/invalid-request-error'); -exports.InvalidScopeError = require('./lib/errors/invalid-scope-error'); -exports.InvalidTokenError = require('./lib/errors/invalid-token-error'); -exports.OAuthError = require('./lib/errors/oauth-error'); -exports.ServerError = require('./lib/errors/server-error'); -exports.UnauthorizedClientError = require('./lib/errors/unauthorized-client-error'); -exports.UnauthorizedRequestError = require('./lib/errors/unauthorized-request-error'); -exports.UnsupportedGrantTypeError = require('./lib/errors/unsupported-grant-type-error'); -exports.UnsupportedResponseTypeError = require('./lib/errors/unsupported-response-type-error'); - diff --git a/index.ts b/index.ts new file mode 100755 index 000000000..3cea1819c --- /dev/null +++ b/index.ts @@ -0,0 +1,10 @@ +export * from './lib/errors'; +export * from './lib/grant-types'; +export * from './lib/handlers'; +export * from './lib/interfaces'; +export { Request } from './lib/request'; +export { Response } from './lib/response'; +export * from './lib/response-types'; +export { OAuth2Server } from './lib/server'; +export * from './lib/token-types'; +export * from './lib/validator/is'; diff --git a/lib/constants/common.ts b/lib/constants/common.ts new file mode 100644 index 000000000..d8caa68f2 --- /dev/null +++ b/lib/constants/common.ts @@ -0,0 +1,12 @@ +export const MILLISECONDS_PER_SECOND = 1_000; +export const SECONDS_PER_MINUTE = 60; +export const MINUTES_PER_HOUR = 60; +export const HOURS_PER_DAY = 24; +export const DAYS_PER_WEEK = 7; +export const MONTHS_PER_YEAR = 12; + +export const SECOND = MILLISECONDS_PER_SECOND; +export const MINUTE = SECONDS_PER_MINUTE * SECOND; +export const HOUR = MINUTES_PER_HOUR * MINUTE; +export const DAY = HOURS_PER_DAY * HOUR; +export const WEEK = DAYS_PER_WEEK * DAY; diff --git a/lib/constants/index.ts b/lib/constants/index.ts new file mode 100644 index 000000000..d0b932366 --- /dev/null +++ b/lib/constants/index.ts @@ -0,0 +1 @@ +export * from './common'; diff --git a/lib/errors/access-denied-error.js b/lib/errors/access-denied-error.js deleted file mode 100644 index d3ffc704a..000000000 --- a/lib/errors/access-denied-error.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); - -/** - * Constructor. - * - * "The resource owner or authorization server denied the request" - * - * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 - */ - -function AccessDeniedError(message, properties) { - properties = _.assign({ - code: 400, - name: 'access_denied' - }, properties); - - OAuthError.call(this, message, properties); -} - -/** - * Inherit prototype. - */ - -util.inherits(AccessDeniedError, OAuthError); - -/** - * Export constructor. - */ - -module.exports = AccessDeniedError; diff --git a/lib/errors/access-denied-error.ts b/lib/errors/access-denied-error.ts new file mode 100755 index 000000000..d78e98d00 --- /dev/null +++ b/lib/errors/access-denied-error.ts @@ -0,0 +1,15 @@ +import { OAuthError } from './oauth-error'; + +/** + * Constructor. + * + * "The resource owner or authorization server denied the request" + * + * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + */ + +export class AccessDeniedError extends OAuthError { + constructor(message: string | Error = '', properties?: any) { + super(message, { code: 400, name: 'access_denied', ...properties }); + } +} diff --git a/lib/errors/index.ts b/lib/errors/index.ts new file mode 100644 index 000000000..24141cb6f --- /dev/null +++ b/lib/errors/index.ts @@ -0,0 +1,14 @@ +export { AccessDeniedError } from './access-denied-error'; +export { InsufficientScopeError } from './insufficient-scope-error'; +export { InvalidArgumentError } from './invalid-argument-error'; +export { InvalidClientError } from './invalid-client-error'; +export { InvalidGrantError } from './invalid-grant-error'; +export { InvalidRequestError } from './invalid-request-error'; +export { InvalidScopeError } from './invalid-scope-error'; +export { InvalidTokenError } from './invalid-token-error'; +export { OAuthError } from './oauth-error'; +export { ServerError } from './server-error'; +export { UnauthorizedClientError } from './unauthorized-client-error'; +export { UnauthorizedRequestError } from './unauthorized-request-error'; +export { UnsupportedGrantTypeError } from './unsupported-grant-type-error'; +export { UnsupportedResponseTypeError } from './unsupported-response-type-error'; diff --git a/lib/errors/insufficient-scope-error.js b/lib/errors/insufficient-scope-error.js deleted file mode 100644 index c6442eac6..000000000 --- a/lib/errors/insufficient-scope-error.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); - -/** - * Constructor. - * - * "The request requires higher privileges than provided by the access token.." - * - * @see https://tools.ietf.org/html/rfc6750.html#section-3.1 - */ - -function InsufficientScopeError(message, properties) { - properties = _.assign({ - code: 403, - name: 'insufficient_scope' - }, properties); - - OAuthError.call(this, message, properties); -} - -/** - * Inherit prototype. - */ - -util.inherits(InsufficientScopeError, OAuthError); - -/** - * Export constructor. - */ - -module.exports = InsufficientScopeError; diff --git a/lib/errors/insufficient-scope-error.ts b/lib/errors/insufficient-scope-error.ts new file mode 100755 index 000000000..c63720a62 --- /dev/null +++ b/lib/errors/insufficient-scope-error.ts @@ -0,0 +1,15 @@ +import { OAuthError } from './oauth-error'; + +/** + * Constructor. + * + * "The request requires higher privileges than provided by the access token." + * + * @see https://tools.ietf.org/html/rfc6750.html#section-3.1 + */ + +export class InsufficientScopeError extends OAuthError { + constructor(message: string | Error = '', properties?: any) { + super(message, { code: 403, name: 'insufficient_scope', ...properties }); + } +} diff --git a/lib/errors/invalid-argument-error.js b/lib/errors/invalid-argument-error.js deleted file mode 100644 index cb56d5a8b..000000000 --- a/lib/errors/invalid-argument-error.js +++ /dev/null @@ -1,34 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); - -/** - * Constructor. - */ - -function InvalidArgumentError(message, properties) { - properties = _.assign({ - code: 500, - name: 'invalid_argument' - }, properties); - - OAuthError.call(this, message, properties); -} - -/** - * Inherit prototype. - */ - -util.inherits(InvalidArgumentError, OAuthError); - -/** - * Export constructor. - */ - -module.exports = InvalidArgumentError; diff --git a/lib/errors/invalid-argument-error.ts b/lib/errors/invalid-argument-error.ts new file mode 100755 index 000000000..393dee964 --- /dev/null +++ b/lib/errors/invalid-argument-error.ts @@ -0,0 +1,12 @@ +import { OAuthError } from './oauth-error'; +/** + * Constructor. + * + * "The request requires valid argument." + * + */ +export class InvalidArgumentError extends OAuthError { + constructor(message: string | Error = '', properties?: any) { + super(message, { code: 500, name: 'invalid_argument', ...properties }); + } +} diff --git a/lib/errors/invalid-client-error.js b/lib/errors/invalid-client-error.js deleted file mode 100644 index d95358c7c..000000000 --- a/lib/errors/invalid-client-error.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); - -/** - * Constructor. - * - * "Client authentication failed (e.g., unknown client, no client - * authentication included, or unsupported authentication method)" - * - * @see https://tools.ietf.org/html/rfc6749#section-5.2 - */ - -function InvalidClientError(message, properties) { - properties = _.assign({ - code: 400, - name: 'invalid_client' - }, properties); - - OAuthError.call(this, message, properties); -} - -/** - * Inherit prototype. - */ - -util.inherits(InvalidClientError, OAuthError); - -/** - * Export constructor. - */ - -module.exports = InvalidClientError; diff --git a/lib/errors/invalid-client-error.ts b/lib/errors/invalid-client-error.ts new file mode 100755 index 000000000..1b097b046 --- /dev/null +++ b/lib/errors/invalid-client-error.ts @@ -0,0 +1,16 @@ +import { OAuthError } from './oauth-error'; + +/** + * Constructor. + * + * "Client authentication failed (e.g., unknown client, no client + * authentication included, or unsupported authentication method)" + * + * @see https://tools.ietf.org/html/rfc6749#section-5.2 + */ + +export class InvalidClientError extends OAuthError { + constructor(message: string | Error = '', properties?: any) { + super(message, { code: 400, name: 'invalid_client', ...properties }); + } +} diff --git a/lib/errors/invalid-grant-error.js b/lib/errors/invalid-grant-error.js deleted file mode 100644 index 58d032e11..000000000 --- a/lib/errors/invalid-grant-error.js +++ /dev/null @@ -1,40 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); - -/** - * Constructor. - * - * "The provided authorization grant (e.g., authorization code, resource owner credentials) - * or refresh token is invalid, expired, revoked, does not match the redirection URI used - * in the authorization request, or was issued to another client." - * - * @see https://tools.ietf.org/html/rfc6749#section-5.2 - */ - -function InvalidGrantError(message, properties) { - properties = _.assign({ - code: 400, - name: 'invalid_grant' - }, properties); - - OAuthError.call(this, message, properties); -} - -/** - * Inherit prototype. - */ - -util.inherits(InvalidGrantError, OAuthError); - -/** - * Export constructor. - */ - -module.exports = InvalidGrantError; diff --git a/lib/errors/invalid-grant-error.ts b/lib/errors/invalid-grant-error.ts new file mode 100755 index 000000000..046ef1902 --- /dev/null +++ b/lib/errors/invalid-grant-error.ts @@ -0,0 +1,17 @@ +import { OAuthError } from './oauth-error'; + +/** + * Constructor. + * + * "The provided authorization grant (e.g., authorization code, resource owner credentials) + * or refresh token is invalid, expired, revoked, does not match the redirection URI used + * in the authorization request, or was issued to another client." + * + * @see https://tools.ietf.org/html/rfc6749#section-5.2 + */ + +export class InvalidGrantError extends OAuthError { + constructor(message: string | Error = '', properties?: any) { + super(message, { code: 400, name: 'invalid_grant', ...properties }); + } +} diff --git a/lib/errors/invalid-request-error.js b/lib/errors/invalid-request-error.js deleted file mode 100644 index 4cf0a73b1..000000000 --- a/lib/errors/invalid-request-error.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); - -/** - * Constructor. - * - * "The request is missing a required parameter, includes an invalid parameter value, - * includes a parameter more than once, or is otherwise malformed." - * - * @see https://tools.ietf.org/html/rfc6749#section-4.2.2.1 - */ - -function InvalidRequest(message, properties) { - properties = _.assign({ - code: 400, - name: 'invalid_request' - }, properties); - - OAuthError.call(this, message, properties); -} - -/** - * Inherit prototype. - */ - -util.inherits(InvalidRequest, OAuthError); - -/** - * Export constructor. - */ - -module.exports = InvalidRequest; diff --git a/lib/errors/invalid-request-error.ts b/lib/errors/invalid-request-error.ts new file mode 100755 index 000000000..d77b8683b --- /dev/null +++ b/lib/errors/invalid-request-error.ts @@ -0,0 +1,16 @@ +import { OAuthError } from './oauth-error'; + +/** + * Constructor. + * + * "The request is missing a required parameter, includes an invalid parameter value, + * includes a parameter more than once, or is otherwise malformed." + * + * @see https://tools.ietf.org/html/rfc6749#section-4.2.2.1 + */ + +export class InvalidRequestError extends OAuthError { + constructor(message: string | Error = '', properties?: any) { + super(message, { code: 400, name: 'invalid_request', ...properties }); + } +} diff --git a/lib/errors/invalid-scope-error.js b/lib/errors/invalid-scope-error.js deleted file mode 100644 index c3b287fc5..000000000 --- a/lib/errors/invalid-scope-error.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); - -/** - * Constructor. - * - * "The requested scope is invalid, unknown, or malformed." - * - * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 - */ - -function InvalidScopeError(message, properties) { - properties = _.assign({ - code: 400, - name: 'invalid_scope' - }, properties); - - OAuthError.call(this, message, properties); -} - -/** - * Inherit prototype. - */ - -util.inherits(InvalidScopeError, OAuthError); - -/** - * Export constructor. - */ - -module.exports = InvalidScopeError; diff --git a/lib/errors/invalid-scope-error.ts b/lib/errors/invalid-scope-error.ts new file mode 100755 index 000000000..9611d3aac --- /dev/null +++ b/lib/errors/invalid-scope-error.ts @@ -0,0 +1,15 @@ +import { OAuthError } from './oauth-error'; + +/** + * Constructor. + * + * "The requested scope is invalid, unknown, or malformed." + * + * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + */ + +export class InvalidScopeError extends OAuthError { + constructor(message: string | Error = '', properties?: any) { + super(message, { code: 400, name: 'invalid_scope', ...properties }); + } +} diff --git a/lib/errors/invalid-token-error.js b/lib/errors/invalid-token-error.js deleted file mode 100644 index d7e7a8bfe..000000000 --- a/lib/errors/invalid-token-error.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); - -/** - * Constructor. - * - * "The access token provided is expired, revoked, malformed, or invalid for other reasons." - * - * @see https://tools.ietf.org/html/rfc6750#section-3.1 - */ - -function InvalidTokenError(message, properties) { - properties = _.assign({ - code: 401, - name: 'invalid_token' - }, properties); - - OAuthError.call(this, message, properties); -} - -/** - * Inherit prototype. - */ - -util.inherits(InvalidTokenError, OAuthError); - -/** - * Export constructor. - */ - -module.exports = InvalidTokenError; diff --git a/lib/errors/invalid-token-error.ts b/lib/errors/invalid-token-error.ts new file mode 100755 index 000000000..13f8a97df --- /dev/null +++ b/lib/errors/invalid-token-error.ts @@ -0,0 +1,15 @@ +import { OAuthError } from './oauth-error'; + +/** + * Constructor.invalid_token + * + * "The access token provided is expired, revoked, malformed, or invalid for other reasons." + * + * @see https://tools.ietf.org/html/rfc6750#section-3.1 + */ + +export class InvalidTokenError extends OAuthError { + constructor(message: string | Error = '', properties?: any) { + super(message, { code: 401, name: 'invalid_token', ...properties }); + } +} diff --git a/lib/errors/oauth-error.js b/lib/errors/oauth-error.js deleted file mode 100644 index cd7d33930..000000000 --- a/lib/errors/oauth-error.js +++ /dev/null @@ -1,45 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ -var _ = require('lodash'); -var util = require('util'); -var statuses = require('statuses'); -/** - * Constructor. - */ - -function OAuthError(messageOrError, properties) { - var message = messageOrError instanceof Error ? messageOrError.message : messageOrError; - var error = messageOrError instanceof Error ? messageOrError : null; - if (_.isEmpty(properties)) - { - properties = {}; - } - - _.defaults(properties, { code: 500 }); - - if (error) { - properties.inner = error; - } - if (_.isEmpty(message)) { - message = statuses[properties.code]; - } - this.code = this.status = this.statusCode = properties.code; - this.message = message; - for (var key in properties) { - if (key !== 'code') { - this[key] = properties[key]; - } - } - Error.captureStackTrace(this, OAuthError); -} - -util.inherits(OAuthError, Error); - -/** - * Export constructor. - */ - -module.exports = OAuthError; diff --git a/lib/errors/oauth-error.ts b/lib/errors/oauth-error.ts new file mode 100755 index 000000000..3fd32de5c --- /dev/null +++ b/lib/errors/oauth-error.ts @@ -0,0 +1,32 @@ +import * as statuses from 'statuses'; + +export class OAuthError extends Error { + code: any; + status: any; + statusCode: any; + constructor(messageOrError: string | Error, properties: any = {}) { + super(); + let message = + messageOrError instanceof Error ? messageOrError.message : messageOrError; + const error = messageOrError instanceof Error ? messageOrError : undefined; + let props: any = {}; + props = properties; + props.code = props.code || 500; // default code 500 + + if (error) { + props.inner = error; + } + if (!message) { + message = statuses[props.code]; + } + this.code = this.status = this.statusCode = props.code; + this.message = message; + + const ignoreAttr = ['code', 'message']; + Object.keys(props) + .filter(key => !ignoreAttr.includes(key)) + .forEach(key => (this[key] = props[key])); + + Error.captureStackTrace(this, OAuthError); + } +} diff --git a/lib/errors/server-error.js b/lib/errors/server-error.js deleted file mode 100644 index d193af39c..000000000 --- a/lib/errors/server-error.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); - -/** - * Constructor. - * - * "The authorization server encountered an unexpected condition that prevented it from fulfilling the request." - * - * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 - */ - -function ServerError(message, properties) { - properties = _.assign({ - code: 503, - name: 'server_error' - }, properties); - - OAuthError.call(this, message, properties); -} - -/** - * Inherit prototype. - */ - -util.inherits(ServerError, OAuthError); - -/** - * Export constructor. - */ - -module.exports = ServerError; diff --git a/lib/errors/server-error.ts b/lib/errors/server-error.ts new file mode 100755 index 000000000..bccc22a41 --- /dev/null +++ b/lib/errors/server-error.ts @@ -0,0 +1,15 @@ +import { OAuthError } from './oauth-error'; + +/** + * ServerError + * + * "The authorization server encountered an unexpected condition that prevented it from fulfilling the request." + * + * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + */ + +export class ServerError extends OAuthError { + constructor(message: string | Error = '', properties?: any) { + super(message, { code: 500, name: 'server_error', ...properties }); + } +} diff --git a/lib/errors/unauthorized-client-error.js b/lib/errors/unauthorized-client-error.js deleted file mode 100644 index c05075d3d..000000000 --- a/lib/errors/unauthorized-client-error.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); - -/** - * Constructor. - * - * "The authenticated client is not authorized to use this authorization grant type." - * - * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 - */ - -function UnauthorizedClientError(message, properties) { - properties = _.assign({ - code: 400, - name: 'unauthorized_client' - }, properties); - - OAuthError.call(this, message, properties); -} - -/** - * Inherit prototype. - */ - -util.inherits(UnauthorizedClientError, OAuthError); - -/** - * Export constructor. - */ - -module.exports = UnauthorizedClientError; diff --git a/lib/errors/unauthorized-client-error.ts b/lib/errors/unauthorized-client-error.ts new file mode 100755 index 000000000..14f3fa0ae --- /dev/null +++ b/lib/errors/unauthorized-client-error.ts @@ -0,0 +1,15 @@ +import { OAuthError } from './oauth-error'; + +/** + * Constructor. + * + * "The authenticated client is not authorized to use this authorization grant type." + * + * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + */ + +export class UnauthorizedClientError extends OAuthError { + constructor(message: string | Error = '', properties?: any) { + super(message, { code: 400, name: 'unauthorized_client', ...properties }); + } +} diff --git a/lib/errors/unauthorized-request-error.js b/lib/errors/unauthorized-request-error.js deleted file mode 100644 index ae7500d9c..000000000 --- a/lib/errors/unauthorized-request-error.js +++ /dev/null @@ -1,41 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); - -/** - * Constructor. - * - * "If the request lacks any authentication information (e.g., the client - * was unaware that authentication is necessary or attempted using an - * unsupported authentication method), the resource server SHOULD NOT - * include an error code or other error information." - * - * @see https://tools.ietf.org/html/rfc6750#section-3.1 - */ - -function UnauthorizedRequestError(message, properties) { - properties = _.assign({ - code: 401, - name: 'unauthorized_request' - }, properties); - - OAuthError.call(this, message, properties); -} - -/** - * Inherit prototype. - */ - -util.inherits(UnauthorizedRequestError, OAuthError); - -/** - * Export constructor. - */ - -module.exports = UnauthorizedRequestError; diff --git a/lib/errors/unauthorized-request-error.ts b/lib/errors/unauthorized-request-error.ts new file mode 100755 index 000000000..2de582c43 --- /dev/null +++ b/lib/errors/unauthorized-request-error.ts @@ -0,0 +1,18 @@ +import { OAuthError } from './oauth-error'; + +/** + * Constructor. + * + * "If the request lacks any authentication information (e.g., the client + * was unaware that authentication is necessary or attempted using an + * unsupported authentication method), the resource server SHOULD NOT + * include an error code or other error information." + * + * @see https://tools.ietf.org/html/rfc6750#section-3.1 + */ + +export class UnauthorizedRequestError extends OAuthError { + constructor(message: string | Error = '', properties?: any) { + super(message, { code: 401, name: 'unauthorized_request', ...properties }); + } +} diff --git a/lib/errors/unsupported-grant-type-error.js b/lib/errors/unsupported-grant-type-error.js deleted file mode 100644 index 28ca0ec04..000000000 --- a/lib/errors/unsupported-grant-type-error.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); - -/** - * Constructor. - * - * "The authorization grant type is not supported by the authorization server." - * - * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 - */ - -function UnsupportedGrantTypeError(message, properties) { - properties = _.assign({ - code: 400, - name: 'unsupported_grant_type' - }, properties); - - OAuthError.call(this, message, properties); -} - -/** - * Inherit prototype. - */ - -util.inherits(UnsupportedGrantTypeError, OAuthError); - -/** - * Export constructor. - */ - -module.exports = UnsupportedGrantTypeError; diff --git a/lib/errors/unsupported-grant-type-error.ts b/lib/errors/unsupported-grant-type-error.ts new file mode 100755 index 000000000..5174c6bce --- /dev/null +++ b/lib/errors/unsupported-grant-type-error.ts @@ -0,0 +1,19 @@ +import { OAuthError } from './oauth-error'; + +/** + * Constructor. + * + * "The authorization grant type is not supported by the authorization server." + * + * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + */ + +export class UnsupportedGrantTypeError extends OAuthError { + constructor(message: string | Error = '', properties?: any) { + super(message, { + code: 400, + name: 'unsupported_grant_type', + ...properties, + }); + } +} diff --git a/lib/errors/unsupported-response-type-error.js b/lib/errors/unsupported-response-type-error.js deleted file mode 100644 index 523cc449b..000000000 --- a/lib/errors/unsupported-response-type-error.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var _ = require('lodash'); -var OAuthError = require('./oauth-error'); -var util = require('util'); - -/** - * Constructor. - * - * "The authorization server does not supported obtaining an - * authorization code using this method." - * - * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 - */ - -function UnsupportedResponseTypeError(message, properties) { - properties = _.assign({ - code: 400, - name: 'unsupported_response_type' - }, properties); - - OAuthError.call(this, message, properties); -} - -/** - * Inherit prototype. - */ - -util.inherits(UnsupportedResponseTypeError, OAuthError); - -/** - * Export constructor. - */ - -module.exports = UnsupportedResponseTypeError; diff --git a/lib/errors/unsupported-response-type-error.ts b/lib/errors/unsupported-response-type-error.ts new file mode 100755 index 000000000..9e7cd1471 --- /dev/null +++ b/lib/errors/unsupported-response-type-error.ts @@ -0,0 +1,20 @@ +import { OAuthError } from './oauth-error'; + +/** + * Constructor. + * + * "The authorization server does not supported obtaining an + * authorization code using this method." + * + * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + */ + +export class UnsupportedResponseTypeError extends OAuthError { + constructor(message: string | Error = '', properties?: any) { + super(message, { + code: 400, + name: 'unsupported_response_type', + ...properties, + }); + } +} diff --git a/lib/grant-types/abstract-grant-type.ts b/lib/grant-types/abstract-grant-type.ts new file mode 100755 index 000000000..8d9adea38 --- /dev/null +++ b/lib/grant-types/abstract-grant-type.ts @@ -0,0 +1,112 @@ +import { MILLISECONDS_PER_SECOND } from '../constants'; +import { InvalidArgumentError, InvalidScopeError } from '../errors'; +import { Client, Model, User } from '../interfaces'; +import { Request } from '../request'; +import * as tokenUtil from '../utils/token-util'; +import * as is from '../validator/is'; + +export class AbstractGrantType { + accessTokenLifetime: number; + model: Model; + refreshTokenLifetime: number; + alwaysIssueNewRefreshToken: boolean; + + constructor(options: any = {}) { + if (!options.accessTokenLifetime) { + throw new InvalidArgumentError( + 'Missing parameter: `accessTokenLifetime`', + ); + } + + if (!options.model) { + throw new InvalidArgumentError('Missing parameter: `model`'); + } + + this.accessTokenLifetime = options.accessTokenLifetime; + this.model = options.model; + this.refreshTokenLifetime = options.refreshTokenLifetime; + this.alwaysIssueNewRefreshToken = options.alwaysIssueNewRefreshToken; + } + + /** + * Generate access token. + */ + + async generateAccessToken(client?: Client, user?: User, scope?: string) { + if (this.model.generateAccessToken) { + const token = await this.model.generateAccessToken(client, user, scope); + + return token ? token : tokenUtil.GenerateRandomToken(); + } + + return tokenUtil.GenerateRandomToken(); + } + + /** + * Generate refresh token. + */ + + async generateRefreshToken(client?: Client, user?: User, scope?: string) { + if (this.model.generateRefreshToken) { + const token = await this.model.generateRefreshToken(client, user, scope); + + return token ? token : tokenUtil.GenerateRandomToken(); + } + + return tokenUtil.GenerateRandomToken(); + } + + /** + * Get access token expiration date. + */ + + getAccessTokenExpiresAt() { + return new Date( + Date.now() + this.accessTokenLifetime * MILLISECONDS_PER_SECOND, + ); + } + + /** + * Get refresh token expiration date. + */ + + getRefreshTokenExpiresAt() { + return new Date( + Date.now() + this.refreshTokenLifetime * MILLISECONDS_PER_SECOND, + ); + } + + /** + * Get scope from the request body. + */ + + getScope(request: Request) { + if (!is.nqschar(request.body.scope)) { + throw new InvalidArgumentError('Invalid parameter: `scope`'); + } + + return request.body.scope; + } + + /** + * Validate requested scope. + */ + async validateScope(user: User, client: Client, scope: string) { + if (this.model.validateScope) { + const validatedScope = await this.model.validateScope( + user, + client, + scope, + ); + if (!validatedScope) { + throw new InvalidScopeError( + 'Invalid scope: Requested scope is invalid', + ); + } + + return validatedScope; + } + + return scope; + } +} diff --git a/lib/grant-types/authorization-code-grant-type.js b/lib/grant-types/authorization-code-grant-type.js deleted file mode 100644 index 7eae70f8f..000000000 --- a/lib/grant-types/authorization-code-grant-type.js +++ /dev/null @@ -1,206 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var AbstractGrantType = require('./abstract-grant-type'); -var InvalidArgumentError = require('../errors/invalid-argument-error'); -var InvalidGrantError = require('../errors/invalid-grant-error'); -var InvalidRequestError = require('../errors/invalid-request-error'); -var Promise = require('bluebird'); -var promisify = require('promisify-any').use(Promise); -var ServerError = require('../errors/server-error'); -var is = require('../validator/is'); -var util = require('util'); - -/** - * Constructor. - */ - -function AuthorizationCodeGrantType(options) { - options = options || {}; - - if (!options.model) { - throw new InvalidArgumentError('Missing parameter: `model`'); - } - - if (!options.model.getAuthorizationCode) { - throw new InvalidArgumentError('Invalid argument: model does not implement `getAuthorizationCode()`'); - } - - if (!options.model.revokeAuthorizationCode) { - throw new InvalidArgumentError('Invalid argument: model does not implement `revokeAuthorizationCode()`'); - } - - if (!options.model.saveToken) { - throw new InvalidArgumentError('Invalid argument: model does not implement `saveToken()`'); - } - - AbstractGrantType.call(this, options); -} - -/** - * Inherit prototype. - */ - -util.inherits(AuthorizationCodeGrantType, AbstractGrantType); - -/** - * Handle authorization code grant. - * - * @see https://tools.ietf.org/html/rfc6749#section-4.1.3 - */ - -AuthorizationCodeGrantType.prototype.handle = function(request, client) { - if (!request) { - throw new InvalidArgumentError('Missing parameter: `request`'); - } - - if (!client) { - throw new InvalidArgumentError('Missing parameter: `client`'); - } - - return Promise.bind(this) - .then(function() { - return this.getAuthorizationCode(request, client); - }) - .tap(function(code) { - return this.validateRedirectUri(request, code); - }) - .tap(function(code) { - return this.revokeAuthorizationCode(code); - }) - .then(function(code) { - return this.saveToken(code.user, client, code.authorizationCode, code.scope); - }); -}; - -/** - * Get the authorization code. - */ - -AuthorizationCodeGrantType.prototype.getAuthorizationCode = function(request, client) { - if (!request.body.code) { - throw new InvalidRequestError('Missing parameter: `code`'); - } - - if (!is.vschar(request.body.code)) { - throw new InvalidRequestError('Invalid parameter: `code`'); - } - return promisify(this.model.getAuthorizationCode, 1).call(this.model, request.body.code) - .then(function(code) { - if (!code) { - throw new InvalidGrantError('Invalid grant: authorization code is invalid'); - } - - if (!code.client) { - throw new ServerError('Server error: `getAuthorizationCode()` did not return a `client` object'); - } - - if (!code.user) { - throw new ServerError('Server error: `getAuthorizationCode()` did not return a `user` object'); - } - - if (code.client.id !== client.id) { - throw new InvalidGrantError('Invalid grant: authorization code is invalid'); - } - - if (!(code.expiresAt instanceof Date)) { - throw new ServerError('Server error: `expiresAt` must be a Date instance'); - } - - if (code.expiresAt < new Date()) { - throw new InvalidGrantError('Invalid grant: authorization code has expired'); - } - - if (code.redirectUri && !is.uri(code.redirectUri)) { - throw new InvalidGrantError('Invalid grant: `redirect_uri` is not a valid URI'); - } - - return code; - }); -}; - -/** - * Validate the redirect URI. - * - * "The authorization server MUST ensure that the redirect_uri parameter is - * present if the redirect_uri parameter was included in the initial - * authorization request as described in Section 4.1.1, and if included - * ensure that their values are identical." - * - * @see https://tools.ietf.org/html/rfc6749#section-4.1.3 - */ - - AuthorizationCodeGrantType.prototype.validateRedirectUri = function(request, code) { - if (!code.redirectUri) { - return; - } - - var redirectUri = request.body.redirect_uri || request.query.redirect_uri; - - if (!is.uri(redirectUri)) { - throw new InvalidRequestError('Invalid request: `redirect_uri` is not a valid URI'); - } - - if (redirectUri !== code.redirectUri) { - throw new InvalidRequestError('Invalid request: `redirect_uri` is invalid'); - } - }; - -/** - * Revoke the authorization code. - * - * "The authorization code MUST expire shortly after it is issued to mitigate - * the risk of leaks. [...] If an authorization code is used more than once, - * the authorization server MUST deny the request." - * - * @see https://tools.ietf.org/html/rfc6749#section-4.1.2 - */ - -AuthorizationCodeGrantType.prototype.revokeAuthorizationCode = function(code) { - return promisify(this.model.revokeAuthorizationCode, 1).call(this.model, code) - .then(function(status) { - if (!status) { - throw new InvalidGrantError('Invalid grant: authorization code is invalid'); - } - - return code; - }); -}; - -/** - * Save token. - */ - -AuthorizationCodeGrantType.prototype.saveToken = function(user, client, authorizationCode, scope) { - var fns = [ - this.validateScope(user, client, scope), - this.generateAccessToken(client, user, scope), - this.generateRefreshToken(client, user, scope), - this.getAccessTokenExpiresAt(), - this.getRefreshTokenExpiresAt() - ]; - - return Promise.all(fns) - .bind(this) - .spread(function(scope, accessToken, refreshToken, accessTokenExpiresAt, refreshTokenExpiresAt) { - var token = { - accessToken: accessToken, - authorizationCode: authorizationCode, - accessTokenExpiresAt: accessTokenExpiresAt, - refreshToken: refreshToken, - refreshTokenExpiresAt: refreshTokenExpiresAt, - scope: scope - }; - - return promisify(this.model.saveToken, 3).call(this.model, token, client, user); - }); -}; - -/** - * Export constructor. - */ - -module.exports = AuthorizationCodeGrantType; diff --git a/lib/grant-types/authorization-code-grant-type.ts b/lib/grant-types/authorization-code-grant-type.ts new file mode 100755 index 000000000..f00c82c71 --- /dev/null +++ b/lib/grant-types/authorization-code-grant-type.ts @@ -0,0 +1,202 @@ +import { AbstractGrantType } from '.'; +import { + InvalidArgumentError, + InvalidGrantError, + InvalidRequestError, + ServerError, +} from '../errors'; +import { AuthorizationCode, Client, Token, User } from '../interfaces'; +import { Request } from '../request'; +import * as is from '../validator/is'; + +export class AuthorizationCodeGrantType extends AbstractGrantType { + constructor(options: any = {}) { + super(options); + if (!options.model) { + throw new InvalidArgumentError('Missing parameter: `model`'); + } + + if (!options.model.getAuthorizationCode) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `getAuthorizationCode()`', + ); + } + + if (!options.model.revokeAuthorizationCode) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `revokeAuthorizationCode()`', + ); + } + + if (!options.model.saveToken) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `saveToken()`', + ); + } + } + + /** + * Handle authorization code grant. + * + * @see https://tools.ietf.org/html/rfc6749#section-4.1.3 + */ + + async handle(request: Request, client: Client) { + if (!request) { + throw new InvalidArgumentError('Missing parameter: `request`'); + } + + if (!client) { + throw new InvalidArgumentError('Missing parameter: `client`'); + } + const code = await this.getAuthorizationCode(request, client); + this.validateRedirectUri(request, code); + await this.revokeAuthorizationCode(code); + + return this.saveToken( + code.user, + client, + code.authorizationCode, + code.scope, + ); + } + + /** + * Get the authorization code. + */ + + async getAuthorizationCode(request: Request, client: Client) { + if (!request.body.code) { + throw new InvalidRequestError('Missing parameter: `code`'); + } + + if (!is.vschar(request.body.code)) { + throw new InvalidRequestError('Invalid parameter: `code`'); + } + + const code = await this.model.getAuthorizationCode(request.body.code); + if (!code) { + throw new InvalidGrantError( + 'Invalid grant: authorization code is invalid', + ); + } + + if (!code.client) { + throw new ServerError( + 'Server error: `getAuthorizationCode()` did not return a `client` object', + ); + } + + if (!code.user) { + throw new ServerError( + 'Server error: `getAuthorizationCode()` did not return a `user` object', + ); + } + + if (code.client.id !== client.id) { + throw new InvalidGrantError( + 'Invalid grant: authorization code is invalid', + ); + } + + if (!(code.expiresAt instanceof Date)) { + throw new ServerError( + 'Server error: `expiresAt` must be a Date instance', + ); + } + + if (code.expiresAt.getTime() < Date.now()) { + throw new InvalidGrantError( + 'Invalid grant: authorization code has expired', + ); + } + + if (code.redirectUri && !is.uri(code.redirectUri)) { + throw new InvalidGrantError( + 'Invalid grant: `redirect_uri` is not a valid URI', + ); + } + + return code; + } + + /** + * Validate the redirect URI. + * + * "The authorization server MUST ensure that the redirect_uri parameter is + * present if the redirect_uri parameter was included in the initial + * authorization request as described in Section 4.1.1, and if included + * ensure that their values are identical." + * + * @see https://tools.ietf.org/html/rfc6749#section-4.1.3 + */ + + validateRedirectUri(request: Request, code: AuthorizationCode) { + if (!code.redirectUri) { + return; + } + + const redirectUri = request.body.redirect_uri || request.query.redirect_uri; + + if (!is.uri(redirectUri)) { + throw new InvalidRequestError( + 'Invalid request: `redirect_uri` is not a valid URI', + ); + } + + if (redirectUri !== code.redirectUri) { + throw new InvalidRequestError( + 'Invalid request: `redirect_uri` is invalid', + ); + } + } + + /** + * Revoke the authorization code. + * + * "The authorization code MUST expire shortly after it is issued to mitigate + * the risk of leaks. [...] If an authorization code is used more than once, + * the authorization server MUST deny the request." + * + * @see https://tools.ietf.org/html/rfc6749#section-4.1.2 + */ + + async revokeAuthorizationCode(code: AuthorizationCode) { + const status = await this.model.revokeAuthorizationCode(code); + if (!status) { + throw new InvalidGrantError( + 'Invalid grant: authorization code is invalid', + ); + } + + return code; + } + + /** + * Save token. + */ + + async saveToken( + user: User, + client: Client, + authorizationCode: string, + scope: string, + ) { + const accessScope = await this.validateScope(user, client, scope); + const accessToken = await this.generateAccessToken(client, user, scope); + const refreshToken = await this.generateRefreshToken(client, user, scope); + const accessTokenExpiresAt = this.getAccessTokenExpiresAt(); + const refreshTokenExpiresAt = this.getRefreshTokenExpiresAt(); + + const token: Token = { + accessToken, + authorizationCode, + accessTokenExpiresAt, + refreshToken, + refreshTokenExpiresAt, + scope: accessScope, + } as any; + + return this.model.saveToken(token, client, user); + } +} diff --git a/lib/grant-types/client-credentials-grant-type.js b/lib/grant-types/client-credentials-grant-type.js deleted file mode 100644 index 138333e50..000000000 --- a/lib/grant-types/client-credentials-grant-type.js +++ /dev/null @@ -1,111 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var AbstractGrantType = require('./abstract-grant-type'); -var InvalidArgumentError = require('../errors/invalid-argument-error'); -var InvalidGrantError = require('../errors/invalid-grant-error'); -var Promise = require('bluebird'); -var promisify = require('promisify-any').use(Promise); -var util = require('util'); - -/** - * Constructor. - */ - -function ClientCredentialsGrantType(options) { - options = options || {}; - - if (!options.model) { - throw new InvalidArgumentError('Missing parameter: `model`'); - } - - if (!options.model.getUserFromClient) { - throw new InvalidArgumentError('Invalid argument: model does not implement `getUserFromClient()`'); - } - - if (!options.model.saveToken) { - throw new InvalidArgumentError('Invalid argument: model does not implement `saveToken()`'); - } - - AbstractGrantType.call(this, options); -} - -/** - * Inherit prototype. - */ - -util.inherits(ClientCredentialsGrantType, AbstractGrantType); - -/** - * Handle client credentials grant. - * - * @see https://tools.ietf.org/html/rfc6749#section-4.4.2 - */ - -ClientCredentialsGrantType.prototype.handle = function(request, client) { - if (!request) { - throw new InvalidArgumentError('Missing parameter: `request`'); - } - - if (!client) { - throw new InvalidArgumentError('Missing parameter: `client`'); - } - - var scope = this.getScope(request); - - return Promise.bind(this) - .then(function() { - return this.getUserFromClient(client); - }) - .then(function(user) { - return this.saveToken(user, client, scope); - }); -}; - -/** - * Retrieve the user using client credentials. - */ - -ClientCredentialsGrantType.prototype.getUserFromClient = function(client) { - return promisify(this.model.getUserFromClient, 1).call(this.model, client) - .then(function(user) { - if (!user) { - throw new InvalidGrantError('Invalid grant: user credentials are invalid'); - } - - return user; - }); -}; - -/** - * Save token. - */ - -ClientCredentialsGrantType.prototype.saveToken = function(user, client, scope) { - var fns = [ - this.validateScope(user, client, scope), - this.generateAccessToken(client, user, scope), - this.getAccessTokenExpiresAt(client, user, scope) - ]; - - return Promise.all(fns) - .bind(this) - .spread(function(scope, accessToken, accessTokenExpiresAt) { - var token = { - accessToken: accessToken, - accessTokenExpiresAt: accessTokenExpiresAt, - scope: scope - }; - - return promisify(this.model.saveToken, 3).call(this.model, token, client, user); - }); -}; - -/** - * Export constructor. - */ - -module.exports = ClientCredentialsGrantType; diff --git a/lib/grant-types/client-credentials-grant-type.ts b/lib/grant-types/client-credentials-grant-type.ts new file mode 100755 index 000000000..80736bbac --- /dev/null +++ b/lib/grant-types/client-credentials-grant-type.ts @@ -0,0 +1,79 @@ +import { AbstractGrantType } from '.'; +import { InvalidArgumentError, InvalidGrantError } from '../errors'; +import { Client, Token, User } from '../interfaces'; +import { Request } from '../request'; + +export class ClientCredentialsGrantType extends AbstractGrantType { + constructor(options: any = {}) { + super(options); + if (!options.model) { + throw new InvalidArgumentError('Missing parameter: `model`'); + } + + if (!options.model.getUserFromClient) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `getUserFromClient()`', + ); + } + + if (!options.model.saveToken) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `saveToken()`', + ); + } + } + + /** + * Handle client credentials grant. + * + * @see https://tools.ietf.org/html/rfc6749#section-4.4.2 + */ + + async handle(request: Request, client: Client) { + if (!request) { + throw new InvalidArgumentError('Missing parameter: `request`'); + } + + if (!client) { + throw new InvalidArgumentError('Missing parameter: `client`'); + } + + const scope = this.getScope(request); + const user = await this.getUserFromClient(client); + + return this.saveToken(user, client, scope); + } + + /** + * Retrieve the user using client credentials. + */ + + async getUserFromClient(client: Client) { + const user = await this.model.getUserFromClient(client); + if (!user) { + throw new InvalidGrantError( + 'Invalid grant: user credentials are invalid', + ); + } + + return user; + } + + /** + * Save token. + */ + + async saveToken(user: User, client: Client, scope: string) { + const accessScope = await this.validateScope(user, client, scope); + const accessToken = await this.generateAccessToken(client, user, scope); + const accessTokenExpiresAt = this.getAccessTokenExpiresAt(); + + const token = { + accessToken, + accessTokenExpiresAt, + scope: accessScope, + } as Token; + + return this.model.saveToken(token, client, user); + } +} diff --git a/lib/grant-types/implicit-grant-type.ts b/lib/grant-types/implicit-grant-type.ts new file mode 100644 index 000000000..a37670482 --- /dev/null +++ b/lib/grant-types/implicit-grant-type.ts @@ -0,0 +1,63 @@ +import { AbstractGrantType } from '.'; +import { InvalidArgumentError } from '../errors'; +import { Client, Token, User } from '../interfaces'; +import { Request } from '../request'; + +export class ImplicitGrantType extends AbstractGrantType { + scope: string; + user: User; + constructor(options: any = {}) { + super(options); + + if (!options.model) { + throw new InvalidArgumentError('Missing parameter: `model`'); + } + + if (!options.model.saveToken) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `saveToken()`', + ); + } + + if (!options.user) { + throw new InvalidArgumentError('Missing parameter: `user`'); + } + + this.scope = options.scope; + this.user = options.user; + } + + /** + * Handle implicit token grant. + */ + + async handle(request: Request, client: Client) { + if (!request) { + throw new InvalidArgumentError('Missing parameter: `request`'); + } + + if (!client) { + throw new InvalidArgumentError('Missing parameter: `client`'); + } + + return this.saveToken(this.user, client, this.scope); + } + + /** + * Save token. + */ + + async saveToken(user: User, client: Client, scope: string) { + const validatedScope = await this.validateScope(user, client, scope); + const accessToken = await this.generateAccessToken(client, user, scope); + const accessTokenExpiresAt = this.getAccessTokenExpiresAt(); + + const token = { + accessToken, + accessTokenExpiresAt, + scope: validatedScope, + } as Token; + + return this.model.saveToken(token, client, user); + } +} diff --git a/lib/grant-types/index.ts b/lib/grant-types/index.ts new file mode 100644 index 000000000..3d0ad0d4b --- /dev/null +++ b/lib/grant-types/index.ts @@ -0,0 +1,6 @@ +export { AbstractGrantType } from './abstract-grant-type'; +export { AuthorizationCodeGrantType } from './authorization-code-grant-type'; +export { ClientCredentialsGrantType } from './client-credentials-grant-type'; +export { ImplicitGrantType } from './implicit-grant-type'; +export { PasswordGrantType } from './password-grant-type'; +export { RefreshTokenGrantType } from './refresh-token-grant-type'; diff --git a/lib/grant-types/password-grant-type.js b/lib/grant-types/password-grant-type.js deleted file mode 100644 index b7f17935b..000000000 --- a/lib/grant-types/password-grant-type.js +++ /dev/null @@ -1,133 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var AbstractGrantType = require('./abstract-grant-type'); -var InvalidArgumentError = require('../errors/invalid-argument-error'); -var InvalidGrantError = require('../errors/invalid-grant-error'); -var InvalidRequestError = require('../errors/invalid-request-error'); -var Promise = require('bluebird'); -var promisify = require('promisify-any').use(Promise); -var is = require('../validator/is'); -var util = require('util'); - -/** - * Constructor. - */ - -function PasswordGrantType(options) { - options = options || {}; - - if (!options.model) { - throw new InvalidArgumentError('Missing parameter: `model`'); - } - - if (!options.model.getUser) { - throw new InvalidArgumentError('Invalid argument: model does not implement `getUser()`'); - } - - if (!options.model.saveToken) { - throw new InvalidArgumentError('Invalid argument: model does not implement `saveToken()`'); - } - - AbstractGrantType.call(this, options); -} - -/** - * Inherit prototype. - */ - -util.inherits(PasswordGrantType, AbstractGrantType); - -/** - * Retrieve the user from the model using a username/password combination. - * - * @see https://tools.ietf.org/html/rfc6749#section-4.3.2 - */ - -PasswordGrantType.prototype.handle = function(request, client) { - if (!request) { - throw new InvalidArgumentError('Missing parameter: `request`'); - } - - if (!client) { - throw new InvalidArgumentError('Missing parameter: `client`'); - } - - var scope = this.getScope(request); - - return Promise.bind(this) - .then(function() { - return this.getUser(request); - }) - .then(function(user) { - return this.saveToken(user, client, scope); - }); -}; - -/** - * Get user using a username/password combination. - */ - -PasswordGrantType.prototype.getUser = function(request) { - if (!request.body.username) { - throw new InvalidRequestError('Missing parameter: `username`'); - } - - if (!request.body.password) { - throw new InvalidRequestError('Missing parameter: `password`'); - } - - if (!is.uchar(request.body.username)) { - throw new InvalidRequestError('Invalid parameter: `username`'); - } - - if (!is.uchar(request.body.password)) { - throw new InvalidRequestError('Invalid parameter: `password`'); - } - - return promisify(this.model.getUser, 2).call(this.model, request.body.username, request.body.password) - .then(function(user) { - if (!user) { - throw new InvalidGrantError('Invalid grant: user credentials are invalid'); - } - - return user; - }); -}; - -/** - * Save token. - */ - -PasswordGrantType.prototype.saveToken = function(user, client, scope) { - var fns = [ - this.validateScope(user, client, scope), - this.generateAccessToken(client, user, scope), - this.generateRefreshToken(client, user, scope), - this.getAccessTokenExpiresAt(), - this.getRefreshTokenExpiresAt() - ]; - - return Promise.all(fns) - .bind(this) - .spread(function(scope, accessToken, refreshToken, accessTokenExpiresAt, refreshTokenExpiresAt) { - var token = { - accessToken: accessToken, - accessTokenExpiresAt: accessTokenExpiresAt, - refreshToken: refreshToken, - refreshTokenExpiresAt: refreshTokenExpiresAt, - scope: scope - }; - - return promisify(this.model.saveToken, 3).call(this.model, token, client, user); - }); -}; - -/** - * Export constructor. - */ - -module.exports = PasswordGrantType; diff --git a/lib/grant-types/password-grant-type.ts b/lib/grant-types/password-grant-type.ts new file mode 100755 index 000000000..ca07b06ed --- /dev/null +++ b/lib/grant-types/password-grant-type.ts @@ -0,0 +1,108 @@ +import { AbstractGrantType } from '.'; +import { + InvalidArgumentError, + InvalidGrantError, + InvalidRequestError, +} from '../errors'; +import { Client, Token, User } from '../interfaces'; +import { Request } from '../request'; +import * as is from '../validator/is'; + +export class PasswordGrantType extends AbstractGrantType { + constructor(options: any = {}) { + super(options); + + if (!options.model) { + throw new InvalidArgumentError('Missing parameter: `model`'); + } + + if (!options.model.getUser) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `getUser()`', + ); + } + + if (!options.model.saveToken) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `saveToken()`', + ); + } + } + + /** + * Retrieve the user from the model using a username/password combination. + * + * @see https://tools.ietf.org/html/rfc6749#section-4.3.2 + */ + + async handle(request, client) { + if (!request) { + throw new InvalidArgumentError('Missing parameter: `request`'); + } + + if (!client) { + throw new InvalidArgumentError('Missing parameter: `client`'); + } + + const scope = this.getScope(request); + const user = await this.getUser(request); + + return this.saveToken(user, client, scope); + } + + /** + * Get user using a username/password combination. + */ + + async getUser(request: Request) { + if (!request.body.username) { + throw new InvalidRequestError('Missing parameter: `username`'); + } + + if (!request.body.password) { + throw new InvalidRequestError('Missing parameter: `password`'); + } + + if (!is.uchar(request.body.username)) { + throw new InvalidRequestError('Invalid parameter: `username`'); + } + + if (!is.uchar(request.body.password)) { + throw new InvalidRequestError('Invalid parameter: `password`'); + } + + const user = await this.model.getUser( + request.body.username, + request.body.password, + ); + if (!user) { + throw new InvalidGrantError( + 'Invalid grant: user credentials are invalid', + ); + } + + return user; + } + + /** + * Save token. + */ + + async saveToken(user: User, client: Client, scope: string) { + const accessScope = await this.validateScope(user, client, scope); + const accessToken = await this.generateAccessToken(client, user, scope); + const refreshToken = await this.generateRefreshToken(client, user, scope); + const accessTokenExpiresAt = this.getAccessTokenExpiresAt(); + const refreshTokenExpiresAt = this.getRefreshTokenExpiresAt(); + + const token = { + accessToken, + accessTokenExpiresAt, + refreshToken, + refreshTokenExpiresAt, + scope: accessScope, + } as Token; + + return this.model.saveToken(token, client, user); + } +} diff --git a/lib/grant-types/refresh-token-grant-type.js b/lib/grant-types/refresh-token-grant-type.js deleted file mode 100644 index 19f9010c2..000000000 --- a/lib/grant-types/refresh-token-grant-type.js +++ /dev/null @@ -1,180 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var AbstractGrantType = require('./abstract-grant-type'); -var InvalidArgumentError = require('../errors/invalid-argument-error'); -var InvalidGrantError = require('../errors/invalid-grant-error'); -var InvalidRequestError = require('../errors/invalid-request-error'); -var Promise = require('bluebird'); -var promisify = require('promisify-any').use(Promise); -var ServerError = require('../errors/server-error'); -var is = require('../validator/is'); -var util = require('util'); - -/** - * Constructor. - */ - -function RefreshTokenGrantType(options) { - options = options || {}; - - if (!options.model) { - throw new InvalidArgumentError('Missing parameter: `model`'); - } - - if (!options.model.getRefreshToken) { - throw new InvalidArgumentError('Invalid argument: model does not implement `getRefreshToken()`'); - } - - if (!options.model.revokeToken) { - throw new InvalidArgumentError('Invalid argument: model does not implement `revokeToken()`'); - } - - if (!options.model.saveToken) { - throw new InvalidArgumentError('Invalid argument: model does not implement `saveToken()`'); - } - - AbstractGrantType.call(this, options); -} - -/** - * Inherit prototype. - */ - -util.inherits(RefreshTokenGrantType, AbstractGrantType); - -/** - * Handle refresh token grant. - * - * @see https://tools.ietf.org/html/rfc6749#section-6 - */ - -RefreshTokenGrantType.prototype.handle = function(request, client) { - if (!request) { - throw new InvalidArgumentError('Missing parameter: `request`'); - } - - if (!client) { - throw new InvalidArgumentError('Missing parameter: `client`'); - } - - return Promise.bind(this) - .then(function() { - return this.getRefreshToken(request, client); - }) - .tap(function(token) { - return this.revokeToken(token); - }) - .then(function(token) { - return this.saveToken(token.user, client, token.scope); - }); -}; - -/** - * Get refresh token. - */ - -RefreshTokenGrantType.prototype.getRefreshToken = function(request, client) { - if (!request.body.refresh_token) { - throw new InvalidRequestError('Missing parameter: `refresh_token`'); - } - - if (!is.vschar(request.body.refresh_token)) { - throw new InvalidRequestError('Invalid parameter: `refresh_token`'); - } - - return promisify(this.model.getRefreshToken, 1).call(this.model, request.body.refresh_token) - .then(function(token) { - if (!token) { - throw new InvalidGrantError('Invalid grant: refresh token is invalid'); - } - - if (!token.client) { - throw new ServerError('Server error: `getRefreshToken()` did not return a `client` object'); - } - - if (!token.user) { - throw new ServerError('Server error: `getRefreshToken()` did not return a `user` object'); - } - - if (token.client.id !== client.id) { - throw new InvalidGrantError('Invalid grant: refresh token is invalid'); - } - - if (token.refreshTokenExpiresAt && !(token.refreshTokenExpiresAt instanceof Date)) { - throw new ServerError('Server error: `refreshTokenExpiresAt` must be a Date instance'); - } - - if (token.refreshTokenExpiresAt && token.refreshTokenExpiresAt < new Date()) { - throw new InvalidGrantError('Invalid grant: refresh token has expired'); - } - - return token; - }); -}; - -/** - * Revoke the refresh token. - * - * @see https://tools.ietf.org/html/rfc6749#section-6 - */ - -RefreshTokenGrantType.prototype.revokeToken = function(token) { - if (this.alwaysIssueNewRefreshToken === false) { - return Promise.resolve(token); - } - - return promisify(this.model.revokeToken, 1).call(this.model, token) - .then(function(status) { - if (!status) { - throw new InvalidGrantError('Invalid grant: refresh token is invalid'); - } - - return token; - }); -}; - -/** - * Save token. - */ - -RefreshTokenGrantType.prototype.saveToken = function(user, client, scope) { - var fns = [ - this.generateAccessToken(client, user, scope), - this.generateRefreshToken(client, user, scope), - this.getAccessTokenExpiresAt(), - this.getRefreshTokenExpiresAt() - ]; - - return Promise.all(fns) - .bind(this) - .spread(function(accessToken, refreshToken, accessTokenExpiresAt, refreshTokenExpiresAt) { - var token = { - accessToken: accessToken, - accessTokenExpiresAt: accessTokenExpiresAt, - scope: scope - }; - - if (this.alwaysIssueNewRefreshToken !== false) { - token.refreshToken = refreshToken; - token.refreshTokenExpiresAt = refreshTokenExpiresAt; - } - - return token; - }) - .then(function(token) { - return promisify(this.model.saveToken, 3).call(this.model, token, client, user) - .then(function(savedToken) { - return savedToken; - }); - }); -}; - -/** - * Export constructor. - */ - -module.exports = RefreshTokenGrantType; diff --git a/lib/grant-types/refresh-token-grant-type.ts b/lib/grant-types/refresh-token-grant-type.ts new file mode 100755 index 000000000..8e7d962e0 --- /dev/null +++ b/lib/grant-types/refresh-token-grant-type.ts @@ -0,0 +1,158 @@ +import { AbstractGrantType } from '.'; +import { + InvalidArgumentError, + InvalidGrantError, + InvalidRequestError, + ServerError, +} from '../errors'; +import { Client, RefreshToken, User } from '../interfaces'; +import { Request } from '../request'; +import * as is from '../validator/is'; + +export class RefreshTokenGrantType extends AbstractGrantType { + constructor(options: any = {}) { + super(options); + + if (!options.model) { + throw new InvalidArgumentError('Missing parameter: `model`'); + } + + if (!options.model.getRefreshToken) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `getRefreshToken()`', + ); + } + + if (!options.model.revokeToken) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `revokeToken()`', + ); + } + + if (!options.model.saveToken) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `saveToken()`', + ); + } + } + + /** + * Handle refresh token grant. + * + * @see https://tools.ietf.org/html/rfc6749#section-6 + */ + + async handle(request: Request, client: Client) { + if (!request) { + throw new InvalidArgumentError('Missing parameter: `request`'); + } + + if (!client) { + throw new InvalidArgumentError('Missing parameter: `client`'); + } + + const token = await this.getRefreshToken(request, client); + await this.revokeToken(token); + + return this.saveToken(token.user, client, token.scope); + } + + /** + * Get refresh token. + */ + + async getRefreshToken(request: Request, client: Client) { + if (!request.body.refresh_token) { + throw new InvalidRequestError('Missing parameter: `refresh_token`'); + } + + if (!is.vschar(request.body.refresh_token)) { + throw new InvalidRequestError('Invalid parameter: `refresh_token`'); + } + + const token = await this.model.getRefreshToken(request.body.refresh_token); + + if (!token) { + throw new InvalidGrantError('Invalid grant: refresh token is invalid'); + } + + if (!token.client) { + throw new ServerError( + 'Server error: `getRefreshToken()` did not return a `client` object', + ); + } + + if (!token.user) { + throw new ServerError( + 'Server error: `getRefreshToken()` did not return a `user` object', + ); + } + + if (token.client.id !== client.id) { + throw new InvalidGrantError('Invalid grant: refresh token is invalid'); + } + + if ( + token.refreshTokenExpiresAt && + !(token.refreshTokenExpiresAt instanceof Date) + ) { + throw new ServerError( + 'Server error: `refreshTokenExpiresAt` must be a Date instance', + ); + } + + if ( + token.refreshTokenExpiresAt && + token.refreshTokenExpiresAt.getTime() < Date.now() + ) { + throw new InvalidGrantError('Invalid grant: refresh token has expired'); + } + + return token; + } + + /** + * Revoke the refresh token. + * + * @see https://tools.ietf.org/html/rfc6749#section-6 + */ + + async revokeToken(token: RefreshToken) { + if (this.alwaysIssueNewRefreshToken === false) { + return token; + } + + const status = await this.model.revokeToken(token); + if (!status) { + throw new InvalidGrantError('Invalid grant: refresh token is invalid'); + } + + return token; + } + + /** + * Save token. + */ + + async saveToken(user: User, client: Client, scope: string) { + const accessToken = await this.generateAccessToken(client, user, scope); + const refreshToken = await this.generateRefreshToken(client, user, scope); + const accessTokenExpiresAt = this.getAccessTokenExpiresAt(); + const refreshTokenExpiresAt = this.getRefreshTokenExpiresAt(); + + const token: any = { + accessToken, + accessTokenExpiresAt, + scope, + }; + + if (this.alwaysIssueNewRefreshToken !== false) { + token.refreshToken = refreshToken; + token.refreshTokenExpiresAt = refreshTokenExpiresAt; + } + + const savedToken = await this.model.saveToken(token, client, user); + + return savedToken; + } +} diff --git a/lib/handlers/authenticate-handler.js b/lib/handlers/authenticate-handler.js index dc9117b27..cd399800a 100644 --- a/lib/handlers/authenticate-handler.js +++ b/lib/handlers/authenticate-handler.js @@ -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)) { diff --git a/lib/handlers/authenticate-handler.ts b/lib/handlers/authenticate-handler.ts new file mode 100755 index 000000000..67b30b0a1 --- /dev/null +++ b/lib/handlers/authenticate-handler.ts @@ -0,0 +1,273 @@ +import { + InsufficientScopeError, + InvalidArgumentError, + InvalidRequestError, + InvalidTokenError, + OAuthError, + ServerError, + UnauthorizedRequestError, +} from '../errors'; +import { Model, Token } from '../interfaces'; +import { Request } from '../request'; +import { Response } from '../response'; + +export class AuthenticateHandler { + addAcceptedScopesHeader: any; + addAuthorizedScopesHeader: any; + allowBearerTokensInQueryString: any; + model: Model; + scope: any; + constructor(options: any = {}) { + if (!options.model) { + throw new InvalidArgumentError('Missing parameter: `model`'); + } + + if (!options.model.getAccessToken) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `getAccessToken()`', + ); + } + + if (options.scope && options.addAcceptedScopesHeader === undefined) { + throw new InvalidArgumentError( + 'Missing parameter: `addAcceptedScopesHeader`', + ); + } + + if (options.scope && options.addAuthorizedScopesHeader === undefined) { + throw new InvalidArgumentError( + 'Missing parameter: `addAuthorizedScopesHeader`', + ); + } + + if (options.scope && !options.model.verifyScope) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `verifyScope()`', + ); + } + + this.addAcceptedScopesHeader = options.addAcceptedScopesHeader; + this.addAuthorizedScopesHeader = options.addAuthorizedScopesHeader; + this.allowBearerTokensInQueryString = + options.allowBearerTokensInQueryString; + this.model = options.model; + this.scope = options.scope; + } + + /** + * Authenticate Handler. + */ + + async handle(request: Request, response: Response) { + if (!(request instanceof Request)) { + throw new InvalidArgumentError( + 'Invalid argument: `request` must be an instance of Request', + ); + } + + if (!(response instanceof Response)) { + throw new InvalidArgumentError( + 'Invalid argument: `response` must be an instance of Response', + ); + } + + // Extend model object with request + this.model.request = request; + + try { + let token = await this.getTokenFromRequest(request); + token = await this.getAccessToken(token); + this.validateAccessToken(token); + if (this.scope) { + await this.verifyScope(token); + } + this.updateResponse(response, token); + + return token; + } catch (e) { + // Include the "WWW-Authenticate" response header field if the client + // lacks any authentication information. + // + // @see https://tools.ietf.org/html/rfc6750#section-3.1 + if (e instanceof UnauthorizedRequestError) { + response.set('WWW-Authenticate', 'Bearer realm="Service"'); + } + + if (!(e instanceof OAuthError)) { + throw new ServerError(e); + } + + throw e; + } + } + + /** + * Get the token from the header or body, depending on the request. + * + * "Clients MUST NOT use more than one method to transmit the token in each request." + * + * @see https://tools.ietf.org/html/rfc6750#section-2 + */ + + getTokenFromRequest(request: Request) { + const headerToken = request.get('Authorization'); + const queryToken = request.query.access_token; + const bodyToken = request.body.access_token; + + if ([headerToken, queryToken, bodyToken].filter(Boolean).length > 1) { + throw new InvalidRequestError( + 'Invalid request: only one authentication method is allowed', + ); + } + + if (headerToken) { + return this.getTokenFromRequestHeader(request); + } + + if (queryToken) { + return this.getTokenFromRequestQuery(request); + } + + if (bodyToken) { + return this.getTokenFromRequestBody(request); + } + + throw new UnauthorizedRequestError( + 'Unauthorized request: no authentication given', + ); + } + + /** + * Get the token from the request header. + * + * @see http://tools.ietf.org/html/rfc6750#section-2.1 + */ + + getTokenFromRequestHeader(request: Request) { + const token = request.get('Authorization'); + const matches = token.match(/Bearer\s(\S+)/); + + if (!matches) { + throw new InvalidRequestError( + 'Invalid request: malformed authorization header', + ); + } + + return matches[1]; + } + + /** + * Get the token from the request query. + * + * "Don't pass bearer tokens in page URLs: Bearer tokens SHOULD NOT be passed in page + * URLs (for example, as query string parameters). Instead, bearer tokens SHOULD be + * passed in HTTP message headers or message bodies for which confidentiality measures + * are taken. Browsers, web servers, and other software may not adequately secure URLs + * in the browser history, web server logs, and other data structures. If bearer tokens + * are passed in page URLs, attackers might be able to steal them from the history data, + * logs, or other unsecured locations." + * + * @see http://tools.ietf.org/html/rfc6750#section-2.3 + */ + + getTokenFromRequestQuery(request: Request) { + if (!this.allowBearerTokensInQueryString) { + throw new InvalidRequestError( + 'Invalid request: do not send bearer tokens in query URLs', + ); + } + + return request.query.access_token; + } + + /** + * Get the token from the request body. + * + * "The HTTP request method is one for which the request-body has defined semantics. + * In particular, this means that the "GET" method MUST NOT be used." + * + * @see http://tools.ietf.org/html/rfc6750#section-2.2 + */ + + getTokenFromRequestBody(request: Request) { + if (request.method === 'GET') { + throw new InvalidRequestError( + 'Invalid request: token may not be passed in the body when using the GET verb', + ); + } + + if (!request.is('application/x-www-form-urlencoded')) { + throw new InvalidRequestError( + 'Invalid request: content must be application/x-www-form-urlencoded', + ); + } + + return request.body.access_token; + } + + /** + * Get the access token from the model. + */ + + async getAccessToken(token: string) { + const accessToken = await this.model.getAccessToken(token); + if (!accessToken) { + throw new InvalidTokenError('Invalid token: access token is invalid'); + } + + if (!accessToken.user) { + throw new ServerError( + 'Server error: `getAccessToken()` did not return a `user` object', + ); + } + + return accessToken; + } + + /** + * Validate access token. + */ + + validateAccessToken(accessToken: Token) { + if (!(accessToken.accessTokenExpiresAt instanceof Date)) { + throw new ServerError( + 'Server error: `accessTokenExpiresAt` must be a Date instance', + ); + } + + if (accessToken.accessTokenExpiresAt.getTime() < Date.now()) { + throw new InvalidTokenError('Invalid token: access token has expired'); + } + + return accessToken; + } + + /** + * Verify scope. + */ + + async verifyScope(accessToken: Token) { + const scope = await this.model.verifyScope(accessToken, this.scope); + if (!scope) { + throw new InsufficientScopeError( + 'Insufficient scope: authorized scope is insufficient', + ); + } + + return scope; + } + + /** + * Update response. + */ + + updateResponse(response: Response, accessToken: Token) { + if (this.scope && this.addAcceptedScopesHeader) { + response.set('X-Accepted-OAuth-Scopes', this.scope); + } + + if (this.scope && this.addAuthorizedScopesHeader) { + response.set('X-OAuth-Scopes', accessToken.scope); + } + } +} diff --git a/lib/handlers/authorize-handler.js b/lib/handlers/authorize-handler.js index 845e25b55..51d57640b 100644 --- a/lib/handlers/authorize-handler.js +++ b/lib/handlers/authorize-handler.js @@ -78,10 +78,6 @@ AuthorizeHandler.prototype.handle = function(request, response) { throw new InvalidArgumentError('Invalid argument: `response` must be an instance of Response'); } - if ('false' === request.query.allowed) { - return Promise.reject(new AccessDeniedError('Access denied: user denied access to application')); - } - var fns = [ this.getAuthorizationCodeLifetime(), this.getClient(request), @@ -98,6 +94,9 @@ AuthorizeHandler.prototype.handle = function(request, response) { return Promise.bind(this) .then(function() { +<<<<<<< HEAD + scope = this.getScope(request); +======= var requestedScope = this.getScope(request); return this.validateScope(user, client, requestedScope); @@ -108,8 +107,18 @@ AuthorizeHandler.prototype.handle = function(request, response) { return this.generateAuthorizationCode(client, user, scope); }) .then(function(authorizationCode) { +>>>>>>> e11930b8c35537b10f965c7390d7fe58622ba0f8 state = this.getState(request); ResponseType = this.getResponseType(request); + }) + .then(function() { + if ('false' === request.query.allowed) { + return Promise.reject(new AccessDeniedError('Access denied: user denied access to application')); + } + + return this.generateAuthorizationCode(client, user, scope); + }) + .then(function(authorizationCode) { return this.saveAuthorizationCode(authorizationCode, expiresAt, scope, client, uri, user); }) diff --git a/lib/handlers/authorize-handler.ts b/lib/handlers/authorize-handler.ts new file mode 100755 index 000000000..93d67a6e9 --- /dev/null +++ b/lib/handlers/authorize-handler.ts @@ -0,0 +1,377 @@ +import * as url from 'url'; +import { AuthenticateHandler } from '.'; +import { + AccessDeniedError, + InvalidArgumentError, + InvalidClientError, + InvalidRequestError, + InvalidScopeError, + OAuthError, + ServerError, + UnauthorizedClientError, + UnsupportedResponseTypeError, +} from '../errors'; +import { Client, Model, User } from '../interfaces'; +import { Request } from '../request'; +import { Response } from '../response'; +import { CodeResponseType, TokenResponseType } from '../response-types'; +import { hasOwnProperty } from '../utils/fn'; +import * as is from '../validator/is'; + +/** + * Response types. + */ + +const responseTypes = { + code: CodeResponseType, + token: TokenResponseType, +}; + +/** + * Constructor. + */ + +export class AuthorizeHandler { + options: any; + allowEmptyState: boolean; + authenticateHandler: any; + model: Model; + constructor(options: any = {}) { + if (options.authenticateHandler && !options.authenticateHandler.handle) { + throw new InvalidArgumentError( + 'Invalid argument: authenticateHandler does not implement `handle()`', + ); + } + + if (!options.model) { + throw new InvalidArgumentError('Missing parameter: `model`'); + } + + if (!options.model.getClient) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `getClient()`', + ); + } + + this.options = options; + this.allowEmptyState = options.allowEmptyState; + this.authenticateHandler = + options.authenticateHandler || new AuthenticateHandler(options); + this.model = options.model; + } + + /** + * Authorize Handler. + */ + + async handle(request: Request, response: Response) { + if (!(request instanceof Request)) { + throw new InvalidArgumentError( + 'Invalid argument: `request` must be an instance of Request', + ); + } + + if (!(response instanceof Response)) { + throw new InvalidArgumentError( + 'Invalid argument: `response` must be an instance of Response', + ); + } + + if (request.query.allowed === 'false') { + throw new AccessDeniedError( + 'Access denied: user denied access to application', + ); + } + + // Extend model object with request + this.model.request = request; + + const client = await this.getClient(request); + const user = await this.getUser(request, response); + + let scope: string; + let state: string; + let RequestedResponseType: any; + let responseType: any; + const uri = this.getRedirectUri(request, client); + try { + const requestedScope = this.getScope(request); + + const validScope = await this.validateScope(user, client, requestedScope); + scope = validScope; + state = this.getState(request); + RequestedResponseType = this.getResponseType(request, client); + responseType = new RequestedResponseType(this.options); + const codeOrAccessToken = await responseType.handle( + request, + client, + user, + uri, + scope, + ); + const redirectUri = this.buildSuccessRedirectUri(uri, responseType); + this.updateResponse(response, redirectUri, responseType, state); + + return codeOrAccessToken; + } catch (e) { + if (!(e instanceof OAuthError)) { + e = new ServerError(e); + } + + const redirectUri = this.buildErrorRedirectUri(uri, responseType, e); + + this.updateResponse(response, redirectUri, responseType, state); + + throw e; + } + } + + /** + * Get the client from the model. + */ + + async getClient(request: Request) { + const clientId = request.body.client_id || request.query.client_id; + + if (!clientId) { + throw new InvalidRequestError('Missing parameter: `client_id`'); + } + + if (!is.vschar(clientId)) { + throw new InvalidRequestError('Invalid parameter: `client_id`'); + } + + const redirectUri = request.body.redirect_uri || request.query.redirect_uri; + + if (redirectUri && !is.uri(redirectUri)) { + throw new InvalidRequestError( + 'Invalid request: `redirect_uri` is not a valid URI', + ); + } + + const client = await this.model.getClient(clientId); + if (!client) { + throw new InvalidClientError( + 'Invalid client: client credentials are invalid', + ); + } + + if (!client.grants) { + throw new InvalidClientError('Invalid client: missing client `grants`'); + } + + const responseType = + request.body.response_type || request.query.response_type; + const requestedGrantType = + responseType === 'token' ? 'implicit' : 'authorization_code'; + + if (!client.grants.includes(requestedGrantType)) { + throw new UnauthorizedClientError( + 'Unauthorized client: `grant_type` is invalid', + ); + } + + if (!client.redirectUris || client.redirectUris.length === 0) { + throw new InvalidClientError( + 'Invalid client: missing client `redirectUri`', + ); + } + + if (redirectUri && !client.redirectUris.includes(redirectUri)) { + throw new InvalidClientError( + 'Invalid client: `redirect_uri` does not match client value', + ); + } + + return client; + } + + /** + * Validate requested scope. + */ + async validateScope(user: User, client: Client, scope: string) { + if (this.model.validateScope) { + const validatedScope = await this.model.validateScope( + user, + client, + scope, + ); + if (!validatedScope) { + throw new InvalidScopeError( + 'Invalid scope: Requested scope is invalid', + ); + } + + return validatedScope; + } + + return scope; + } + + /** + * Get scope from the request. + */ + + getScope(request: Request) { + const scope = request.body.scope || request.query.scope; + + if (!is.nqschar(scope)) { + throw new InvalidScopeError('Invalid parameter: `scope`'); + } + + return scope; + } + + /** + * Get state from the request. + */ + + getState(request: Request) { + const state = request.body.state || request.query.state; + + if (!this.allowEmptyState && !state) { + throw new InvalidRequestError('Missing parameter: `state`'); + } + + if (!is.vschar(state)) { + throw new InvalidRequestError('Invalid parameter: `state`'); + } + + return state; + } + + /** + * Get user by calling the authenticate middleware. + */ + + async getUser(request: Request, response: Response) { + if (this.authenticateHandler instanceof AuthenticateHandler) { + const data = await this.authenticateHandler.handle(request, response); + + return data.user; + } + + const user = await this.authenticateHandler.handle(request, response); + if (!user) { + throw new ServerError( + 'Server error: `handle()` did not return a `user` object', + ); + } + + return user; + } + + /** + * Get redirect URI. + */ + + getRedirectUri(request: Request, client: Client) { + return ( + request.body.redirect_uri || + request.query.redirect_uri || + client.redirectUris[0] + ); + } + + /** + * Get response type. + */ + + getResponseType(request: Request, client: Client) { + const responseType = + request.body.response_type || request.query.response_type; + + if (!responseType) { + throw new InvalidRequestError('Missing parameter: `response_type`'); + } + + if (!hasOwnProperty(responseTypes, responseType)) { + throw new UnsupportedResponseTypeError( + 'Unsupported response type: `response_type` is not supported', + ); + } + + if ( + responseType === 'token' && + (!client || !client.grants.includes('implicit')) + ) { + throw new UnauthorizedClientError( + 'Unauthorized client: `grant_type` is invalid', + ); + } + + return responseTypes[responseType]; + } + + /** + * Build a successful response that redirects the user-agent to the client-provided url. + */ + + buildSuccessRedirectUri( + redirectUri: string, + responseType: CodeResponseType | TokenResponseType, + ) { + const uri = url.parse(redirectUri); + + return responseType.buildRedirectUri(uri); + } + + /** + * Build an error response that redirects the user-agent to the client-provided url. + */ + + buildErrorRedirectUri( + redirectUri: any, + responseType: CodeResponseType | TokenResponseType, + error: Error, + ) { + let uri = url.parse(redirectUri, true); + + if (responseType) { + uri = responseType.setRedirectUriParam(uri, 'error', error.name); + + if (error.message) { + uri = responseType.setRedirectUriParam( + uri, + 'error_description', + error.message, + ); + } + } else { + uri.query = { + error: error.name, + }; + + if (error.message) { + uri.query.error_description = error.message; + } + } + + return uri; + } + + /** + * Update response with the redirect uri and the state parameter, if available. + */ + + updateResponse( + response: Response, + redirectUri: any, + responseType: CodeResponseType | TokenResponseType, + state: any, + ) { + if (responseType && state) { + // tslint:disable-next-line:no-parameter-reassignment + redirectUri = responseType.setRedirectUriParam( + redirectUri, + 'state', + state, + ); + } else if (state) { + redirectUri.query = redirectUri.query || {}; + redirectUri.query.state = state; + } + + response.redirect(url.format(redirectUri)); + } +} diff --git a/lib/handlers/index.ts b/lib/handlers/index.ts new file mode 100644 index 000000000..7bdf800ee --- /dev/null +++ b/lib/handlers/index.ts @@ -0,0 +1,4 @@ +export { AuthenticateHandler } from './authenticate-handler'; +export { AuthorizeHandler } from './authorize-handler'; +export { RevokeHandler } from './revoke-handler'; +export { TokenHandler } from './token-handler'; diff --git a/lib/handlers/revoke-handler.ts b/lib/handlers/revoke-handler.ts new file mode 100644 index 000000000..0636f99fa --- /dev/null +++ b/lib/handlers/revoke-handler.ts @@ -0,0 +1,341 @@ +import * as auth from 'basic-auth'; +import { + InvalidArgumentError, + InvalidClientError, + InvalidRequestError, + InvalidTokenError, + OAuthError, + ServerError, +} from '../errors'; +import { Client, Model } from '../interfaces'; +import { Request } from '../request'; +import { Response } from '../response'; +import { oneSuccess } from '../utils/fn'; +import * as is from '../validator/is'; + +export class RevokeHandler { + model: Model; + constructor(options: any = {}) { + if (!options.model) { + throw new InvalidArgumentError('Missing parameter: `model`'); + } + + if (!options.model.getClient) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `getClient()`', + ); + } + + if (!options.model.getRefreshToken) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `getRefreshToken()`', + ); + } + + if (!options.model.getAccessToken) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `getAccessToken()`', + ); + } + + if (!options.model.revokeToken) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `revokeToken()`', + ); + } + + this.model = options.model; + } + + /** + * Revoke Handler. + */ + + async handle(request: Request, response: Response) { + if (!(request instanceof Request)) { + throw new InvalidArgumentError( + 'Invalid argument: `request` must be an instance of Request', + ); + } + + if (!(response instanceof Response)) { + throw new InvalidArgumentError( + 'Invalid argument: `response` must be an instance of Response', + ); + } + + if (request.method !== 'POST') { + throw new InvalidRequestError('Invalid request: method must be POST'); + } + + if (!request.is('application/x-www-form-urlencoded')) { + throw new InvalidRequestError( + 'Invalid request: content must be application/x-www-form-urlencoded', + ); + } + // Extend model object with request + this.model.request = request; + + try { + const client = await this.getClient(request, response); + + return this.handleRevokeToken(request, client); + } catch (e) { + let error = e; + if (!(error instanceof OAuthError)) { + error = new ServerError(error); + } + /** + * All necessary information is conveyed in the response code. + * + * Note: invalid tokens do not cause an error response since the client + * cannot handle such an error in a reasonable way. Moreover, the + * purpose of the revocation request, invalidating the particular token, + * is already achieved. + * @see https://tools.ietf.org/html/rfc7009#section-2.2 + */ + if (!(error instanceof InvalidTokenError)) { + this.updateErrorResponse(response, error); + } + + throw error; + } + } + + /** + * Revoke a refresh or access token. + * + * Handle the revoking of refresh tokens, and access tokens if supported / desirable + * RFC7009 specifies that "If the server is unable to locate the token using + * the given hint, it MUST extend its search across all of its supported token types" + */ + + async handleRevokeToken(request: Request, client: Client) { + try { + let token = await this.getTokenFromRequest(request); + token = await oneSuccess([ + this.getAccessToken(token, client), + this.getRefreshToken(token, client), + ]); + + return this.revokeToken(token); + } catch (errors) { + throw errors; + } + } + + /** + * Get the client from the model. + */ + + async getClient(request: Request, response: Response) { + const credentials = this.getClientCredentials(request); + + if (!credentials.clientId) { + throw new InvalidRequestError('Missing parameter: `client_id`'); + } + + if (!credentials.clientSecret) { + throw new InvalidRequestError('Missing parameter: `client_secret`'); + } + + if (!is.vschar(credentials.clientId)) { + throw new InvalidRequestError('Invalid parameter: `client_id`'); + } + + if (!is.vschar(credentials.clientSecret)) { + throw new InvalidRequestError('Invalid parameter: `client_secret`'); + } + try { + const client = await this.model.getClient( + credentials.clientId, + credentials.clientSecret, + ); + if (!client) { + throw new InvalidClientError('Invalid client: client is invalid'); + } + + if (!client.grants) { + throw new ServerError('Server error: missing client `grants`'); + } + + if (!(client.grants instanceof Array)) { + throw new ServerError('Server error: `grants` must be an array'); + } + + return client; + } catch (e) { + // Include the "WWW-Authenticate" response header field if the client + // attempted to authenticate via the "Authorization" request header. + // + // @see https://tools.ietf.org/html/rfc6749#section-5.2. + if (e instanceof InvalidClientError && request.get('authorization')) { + response.set('WWW-Authenticate', 'Basic realm="Service"'); + + throw new InvalidClientError(e, { code: 401 }); + } + + throw e; + } + } + + /** + * Get client credentials. + * + * The client credentials may be sent using the HTTP Basic authentication scheme or, alternatively, + * the `client_id` and `client_secret` can be embedded in the body. + * + * @see https://tools.ietf.org/html/rfc6749#section-2.3.1 + */ + + getClientCredentials(request: Request) { + const credentials = auth(request as any); + + if (credentials) { + return { clientId: credentials.name, clientSecret: credentials.pass }; + } + + if (request.body.client_id && request.body.client_secret) { + return { + clientId: request.body.client_id, + clientSecret: request.body.client_secret, + }; + } + + throw new InvalidClientError( + 'Invalid client: cannot retrieve client credentials', + ); + } + + /** + * Get the token from the body. + * + * @see https://tools.ietf.org/html/rfc7009#section-2.1 + */ + + getTokenFromRequest(request: Request) { + const bodyToken = request.body.token; + + if (!bodyToken) { + throw new InvalidRequestError('Missing parameter: `token`'); + } + + return bodyToken; + } + + /** + * Get refresh token. + */ + + async getRefreshToken(token, client: Client) { + const refreshToken = await this.model.getRefreshToken(token); + if (!refreshToken) { + throw new InvalidTokenError('Invalid token: refresh token is invalid'); + } + + if (!refreshToken.client) { + throw new ServerError( + 'Server error: `getRefreshToken()` did not return a `client` object', + ); + } + + if (!refreshToken.user) { + throw new ServerError( + 'Server error: `getRefreshToken()` did not return a `user` object', + ); + } + + if (refreshToken.client.id !== client.id) { + throw new InvalidClientError('Invalid client: client is invalid'); + } + + if ( + refreshToken.refreshTokenExpiresAt && + !(refreshToken.refreshTokenExpiresAt instanceof Date) + ) { + throw new ServerError( + 'Server error: `refreshTokenExpiresAt` must be a Date instance', + ); + } + + if ( + refreshToken.refreshTokenExpiresAt && + refreshToken.refreshTokenExpiresAt.getTime() < Date.now() + ) { + throw new InvalidTokenError('Invalid token: refresh token has expired'); + } + + return refreshToken; + } + + /** + * Get the access token from the model. + */ + + async getAccessToken(token: string, client: Client) { + const accessToken = await this.model.getAccessToken(token); + if (!accessToken) { + throw new InvalidTokenError('Invalid token: access token is invalid'); + } + + if (!accessToken.client) { + throw new ServerError( + 'Server error: `getAccessToken()` did not return a `client` object', + ); + } + + if (!accessToken.user) { + throw new ServerError( + 'Server error: `getAccessToken()` did not return a `user` object', + ); + } + + if (accessToken.client.id !== client.id) { + throw new InvalidClientError('Invalid client: client is invalid'); + } + + if ( + accessToken.accessTokenExpiresAt && + !(accessToken.accessTokenExpiresAt instanceof Date) + ) { + throw new ServerError('Server error: `expires` must be a Date instance'); + } + + if ( + accessToken.accessTokenExpiresAt && + accessToken.accessTokenExpiresAt.getTime() < Date.now() + ) { + throw new InvalidTokenError('Invalid token: access token has expired.'); + } + + return accessToken; + } + + /** + * Revoke the token. + * + * @see https://tools.ietf.org/html/rfc6749#section-6 + */ + + async revokeToken(token: any) { + const revokedToken = await this.model.revokeToken(token); + if (!revokedToken) { + throw new InvalidTokenError('Invalid token: token is invalid'); + } + + return revokedToken; + } + + /** + * Update response when an error is thrown. + */ + + updateErrorResponse(response: Response, error: OAuthError) { + response.body = { + error: error.name, + error_description: error.message, + }; + + response.status = error.code; + } +} diff --git a/lib/handlers/token-handler.js b/lib/handlers/token-handler.js deleted file mode 100644 index feaad3f54..000000000 --- a/lib/handlers/token-handler.js +++ /dev/null @@ -1,297 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var _ = require('lodash'); -var BearerTokenType = require('../token-types/bearer-token-type'); -var InvalidArgumentError = require('../errors/invalid-argument-error'); -var InvalidClientError = require('../errors/invalid-client-error'); -var InvalidRequestError = require('../errors/invalid-request-error'); -var OAuthError = require('../errors/oauth-error'); -var Promise = require('bluebird'); -var promisify = require('promisify-any').use(Promise); -var Request = require('../request'); -var Response = require('../response'); -var ServerError = require('../errors/server-error'); -var TokenModel = require('../models/token-model'); -var UnauthorizedClientError = require('../errors/unauthorized-client-error'); -var UnsupportedGrantTypeError = require('../errors/unsupported-grant-type-error'); -var auth = require('basic-auth'); -var is = require('../validator/is'); - -/** - * Grant types. - */ - -var grantTypes = { - authorization_code: require('../grant-types/authorization-code-grant-type'), - client_credentials: require('../grant-types/client-credentials-grant-type'), - password: require('../grant-types/password-grant-type'), - refresh_token: require('../grant-types/refresh-token-grant-type') -}; - -/** - * Constructor. - */ - -function TokenHandler(options) { - options = options || {}; - - if (!options.accessTokenLifetime) { - throw new InvalidArgumentError('Missing parameter: `accessTokenLifetime`'); - } - - if (!options.model) { - throw new InvalidArgumentError('Missing parameter: `model`'); - } - - if (!options.refreshTokenLifetime) { - throw new InvalidArgumentError('Missing parameter: `refreshTokenLifetime`'); - } - - if (!options.model.getClient) { - throw new InvalidArgumentError('Invalid argument: model does not implement `getClient()`'); - } - - this.accessTokenLifetime = options.accessTokenLifetime; - this.grantTypes = _.assign({}, grantTypes, options.extendedGrantTypes); - this.model = options.model; - this.refreshTokenLifetime = options.refreshTokenLifetime; - this.allowExtendedTokenAttributes = options.allowExtendedTokenAttributes; - this.requireClientAuthentication = options.requireClientAuthentication || {}; - this.alwaysIssueNewRefreshToken = options.alwaysIssueNewRefreshToken !== false; -} - -/** - * Token Handler. - */ - -TokenHandler.prototype.handle = function(request, response) { - if (!(request instanceof Request)) { - throw new InvalidArgumentError('Invalid argument: `request` must be an instance of Request'); - } - - if (!(response instanceof Response)) { - throw new InvalidArgumentError('Invalid argument: `response` must be an instance of Response'); - } - - if (request.method !== 'POST') { - return Promise.reject(new InvalidRequestError('Invalid request: method must be POST')); - } - - if (!request.is('application/x-www-form-urlencoded')) { - return Promise.reject(new InvalidRequestError('Invalid request: content must be application/x-www-form-urlencoded')); - } - - return Promise.bind(this) - .then(function() { - return this.getClient(request, response); - }) - .then(function(client) { - return this.handleGrantType(request, client); - }) - .tap(function(data) { - var model = new TokenModel(data, {allowExtendedTokenAttributes: this.allowExtendedTokenAttributes}); - var tokenType = this.getTokenType(model); - - this.updateSuccessResponse(response, tokenType); - }).catch(function(e) { - if (!(e instanceof OAuthError)) { - e = new ServerError(e); - } - - this.updateErrorResponse(response, e); - - throw e; - }); -}; - -/** - * Get the client from the model. - */ - -TokenHandler.prototype.getClient = function(request, response) { - var credentials = this.getClientCredentials(request); - var grantType = request.body.grant_type; - - if (!credentials.clientId) { - throw new InvalidRequestError('Missing parameter: `client_id`'); - } - - if (this.isClientAuthenticationRequired(grantType) && !credentials.clientSecret) { - throw new InvalidRequestError('Missing parameter: `client_secret`'); - } - - if (!is.vschar(credentials.clientId)) { - throw new InvalidRequestError('Invalid parameter: `client_id`'); - } - - if (credentials.clientSecret && !is.vschar(credentials.clientSecret)) { - throw new InvalidRequestError('Invalid parameter: `client_secret`'); - } - - return promisify(this.model.getClient, 2).call(this.model, credentials.clientId, credentials.clientSecret) - .then(function(client) { - if (!client) { - throw new InvalidClientError('Invalid client: client is invalid'); - } - - if (!client.grants) { - throw new ServerError('Server error: missing client `grants`'); - } - - if (!(client.grants instanceof Array)) { - throw new ServerError('Server error: `grants` must be an array'); - } - - return client; - }) - .catch(function(e) { - // Include the "WWW-Authenticate" response header field if the client - // attempted to authenticate via the "Authorization" request header. - // - // @see https://tools.ietf.org/html/rfc6749#section-5.2. - if ((e instanceof InvalidClientError) && request.get('authorization')) { - response.set('WWW-Authenticate', 'Basic realm="Service"'); - - throw new InvalidClientError(e, { code: 401 }); - } - - throw e; - }); -}; - -/** - * Get client credentials. - * - * The client credentials may be sent using the HTTP Basic authentication scheme or, alternatively, - * the `client_id` and `client_secret` can be embedded in the body. - * - * @see https://tools.ietf.org/html/rfc6749#section-2.3.1 - */ - -TokenHandler.prototype.getClientCredentials = function(request) { - var credentials = auth(request); - var grantType = request.body.grant_type; - - if (credentials) { - return { clientId: credentials.name, clientSecret: credentials.pass }; - } - - if (request.body.client_id && request.body.client_secret) { - return { clientId: request.body.client_id, clientSecret: request.body.client_secret }; - } - - if (!this.isClientAuthenticationRequired(grantType)) { - if(request.body.client_id) { - return { clientId: request.body.client_id }; - } - } - - throw new InvalidClientError('Invalid client: cannot retrieve client credentials'); -}; - -/** - * Handle grant type. - */ - -TokenHandler.prototype.handleGrantType = function(request, client) { - var grantType = request.body.grant_type; - - if (!grantType) { - throw new InvalidRequestError('Missing parameter: `grant_type`'); - } - - if (!is.nchar(grantType) && !is.uri(grantType)) { - throw new InvalidRequestError('Invalid parameter: `grant_type`'); - } - - if (!_.has(this.grantTypes, grantType)) { - throw new UnsupportedGrantTypeError('Unsupported grant type: `grant_type` is invalid'); - } - - if (!_.includes(client.grants, grantType)) { - throw new UnauthorizedClientError('Unauthorized client: `grant_type` is invalid'); - } - - var accessTokenLifetime = this.getAccessTokenLifetime(client); - var refreshTokenLifetime = this.getRefreshTokenLifetime(client); - var Type = this.grantTypes[grantType]; - - var options = { - accessTokenLifetime: accessTokenLifetime, - model: this.model, - refreshTokenLifetime: refreshTokenLifetime, - alwaysIssueNewRefreshToken: this.alwaysIssueNewRefreshToken - }; - - return new Type(options) - .handle(request, client); -}; - -/** - * Get access token lifetime. - */ - -TokenHandler.prototype.getAccessTokenLifetime = function(client) { - return client.accessTokenLifetime || this.accessTokenLifetime; -}; - -/** - * Get refresh token lifetime. - */ - -TokenHandler.prototype.getRefreshTokenLifetime = function(client) { - return client.refreshTokenLifetime || this.refreshTokenLifetime; -}; - -/** - * Get token type. - */ - -TokenHandler.prototype.getTokenType = function(model) { - return new BearerTokenType(model.accessToken, model.accessTokenLifetime, model.refreshToken, model.scope, model.customAttributes); -}; - -/** - * Update response when a token is generated. - */ - -TokenHandler.prototype.updateSuccessResponse = function(response, tokenType) { - response.body = tokenType.valueOf(); - - response.set('Cache-Control', 'no-store'); - response.set('Pragma', 'no-cache'); -}; - -/** - * Update response when an error is thrown. - */ - -TokenHandler.prototype.updateErrorResponse = function(response, error) { - response.body = { - error: error.name, - error_description: error.message - }; - - response.status = error.code; -}; - -/** - * Given a grant type, check if client authentication is required - */ -TokenHandler.prototype.isClientAuthenticationRequired = function(grantType) { - if (Object.keys(this.requireClientAuthentication).length > 0) { - return (typeof this.requireClientAuthentication[grantType] !== 'undefined') ? this.requireClientAuthentication[grantType] : true; - } else { - return true; - } -}; - -/** - * Export constructor. - */ - -module.exports = TokenHandler; diff --git a/lib/handlers/token-handler.ts b/lib/handlers/token-handler.ts new file mode 100755 index 000000000..66d9ac363 --- /dev/null +++ b/lib/handlers/token-handler.ts @@ -0,0 +1,331 @@ +import * as auth from 'basic-auth'; +import { + InvalidArgumentError, + InvalidClientError, + InvalidRequestError, + OAuthError, + ServerError, + UnauthorizedClientError, + UnsupportedGrantTypeError, +} from '../errors'; +import { + AuthorizationCodeGrantType, + ClientCredentialsGrantType, + PasswordGrantType, + RefreshTokenGrantType, +} from '../grant-types'; +import { Client, Model } from '../interfaces'; +import { TokenModel } from '../models'; +import { Request } from '../request'; +import { Response } from '../response'; +import { BearerTokenType } from '../token-types'; +import { hasOwnProperty } from '../utils/fn'; +import * as is from '../validator/is'; + +/** + * Grant types. + */ + +const grantTypes = { + authorization_code: AuthorizationCodeGrantType, + client_credentials: ClientCredentialsGrantType, + password: PasswordGrantType, + refresh_token: RefreshTokenGrantType, +}; +export class TokenHandler { + accessTokenLifetime: any; + grantTypes: { [key: string]: any }; + model: Model; + refreshTokenLifetime: number; + allowExtendedTokenAttributes: boolean; + requireClientAuthentication: any; + alwaysIssueNewRefreshToken: boolean; + constructor(options: any = {}) { + if (!options.accessTokenLifetime) { + throw new InvalidArgumentError( + 'Missing parameter: `accessTokenLifetime`', + ); + } + + if (!options.model) { + throw new InvalidArgumentError('Missing parameter: `model`'); + } + + if (!options.refreshTokenLifetime) { + throw new InvalidArgumentError( + 'Missing parameter: `refreshTokenLifetime`', + ); + } + + if (!options.model.getClient) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `getClient()`', + ); + } + + this.accessTokenLifetime = options.accessTokenLifetime; + this.grantTypes = { ...grantTypes, ...options.extendedGrantTypes }; + this.model = options.model; + this.refreshTokenLifetime = options.refreshTokenLifetime; + this.allowExtendedTokenAttributes = options.allowExtendedTokenAttributes; + this.requireClientAuthentication = + options.requireClientAuthentication || {}; + this.alwaysIssueNewRefreshToken = + options.alwaysIssueNewRefreshToken !== false; + } + + /** + * Token Handler. + */ + + async handle(request: Request, response: Response) { + if (!(request instanceof Request)) { + throw new InvalidArgumentError( + 'Invalid argument: `request` must be an instance of Request', + ); + } + + if (!(response instanceof Response)) { + throw new InvalidArgumentError( + 'Invalid argument: `response` must be an instance of Response', + ); + } + + if (request.method !== 'POST') { + throw new InvalidRequestError('Invalid request: method must be POST'); + } + + if (!request.is('application/x-www-form-urlencoded')) { + throw new InvalidRequestError( + 'Invalid request: content must be application/x-www-form-urlencoded', + ); + } + + // Extend model object with request + this.model.request = request; + + try { + const client = await this.getClient(request, response); + const data = await this.handleGrantType(request, client); + const model = new TokenModel(data, { + allowExtendedTokenAttributes: this.allowExtendedTokenAttributes, + }); + const tokenType = this.getTokenType(model); + this.updateSuccessResponse(response, tokenType); + + return data; + } catch (e) { + if (!(e instanceof OAuthError)) { + e = new ServerError(e); + } + this.updateErrorResponse(response, e); + throw e; + } + } + + /** + * Get the client from the model. + */ + + async getClient(request, response) { + const credentials = this.getClientCredentials(request); + const grantType = request.body.grant_type; + + if (!credentials.clientId) { + throw new InvalidRequestError('Missing parameter: `client_id`'); + } + + if ( + this.isClientAuthenticationRequired(grantType) && + !credentials.clientSecret + ) { + throw new InvalidRequestError('Missing parameter: `client_secret`'); + } + + if (!is.vschar(credentials.clientId)) { + throw new InvalidRequestError('Invalid parameter: `client_id`'); + } + + if (credentials.clientSecret && !is.vschar(credentials.clientSecret)) { + throw new InvalidRequestError('Invalid parameter: `client_secret`'); + } + try { + const client = await this.model.getClient( + credentials.clientId, + credentials.clientSecret, + ); + if (!client) { + throw new InvalidClientError('Invalid client: client is invalid'); + } + + if (!client.grants) { + throw new ServerError('Server error: missing client `grants`'); + } + + if (!(client.grants instanceof Array)) { + throw new ServerError('Server error: `grants` must be an array'); + } + + return client; + } catch (e) { + // Include the "WWW-Authenticate" response header field if the client + // attempted to authenticate via the "Authorization" request header. + // + // @see https://tools.ietf.org/html/rfc6749#section-5.2. + if (e instanceof InvalidClientError && request.get('authorization')) { + response.set('WWW-Authenticate', 'Basic realm="Service"'); + + throw new InvalidClientError(e, { code: 401 }); + } + + throw e; + } + } + + /** + * Get client credentials. + * + * The client credentials may be sent using the HTTP Basic authentication scheme or, alternatively, + * the `client_id` and `client_secret` can be embedded in the body. + * + * @see https://tools.ietf.org/html/rfc6749#section-2.3.1 + */ + + getClientCredentials(request: Request) { + const credentials = auth(request as any); + const grantType = request.body.grant_type; + + if (credentials) { + return { + clientId: credentials.name, + clientSecret: credentials.pass, + }; + } + + if (request.body.client_id && request.body.client_secret) { + return { + clientId: request.body.client_id, + clientSecret: request.body.client_secret, + }; + } + + if ( + !this.isClientAuthenticationRequired(grantType) && + request.body.client_id + ) { + return { clientId: request.body.client_id }; + } + + throw new InvalidClientError( + 'Invalid client: cannot retrieve client credentials', + ); + } + + /** + * Handle grant type. + */ + + async handleGrantType(request: Request, client: Client) { + const grantType = request.body.grant_type; + + if (!grantType) { + throw new InvalidRequestError('Missing parameter: `grant_type`'); + } + + if (!is.nchar(grantType) && !is.uri(grantType)) { + throw new InvalidRequestError('Invalid parameter: `grant_type`'); + } + + if (!hasOwnProperty(this.grantTypes, grantType)) { + throw new UnsupportedGrantTypeError( + 'Unsupported grant type: `grant_type` is invalid', + ); + } + + if (!client.grants.includes(grantType)) { + throw new UnauthorizedClientError( + 'Unauthorized client: `grant_type` is invalid', + ); + } + + const accessTokenLifetime = this.getAccessTokenLifetime(client); + const refreshTokenLifetime = this.getRefreshTokenLifetime(client); + const GrantType = this.grantTypes[grantType]; + + const options = { + accessTokenLifetime, + model: this.model, + refreshTokenLifetime, + alwaysIssueNewRefreshToken: this.alwaysIssueNewRefreshToken, + }; + + return new GrantType(options).handle(request, client); + } + + /** + * Get access token lifetime. + */ + + getAccessTokenLifetime(client: Client) { + return client.accessTokenLifetime || this.accessTokenLifetime; + } + + /** + * Get refresh token lifetime. + */ + + getRefreshTokenLifetime(client: Client) { + return client.refreshTokenLifetime || this.refreshTokenLifetime; + } + + /** + * Get token type. + */ + + getTokenType(model: any) { + return new BearerTokenType( + model.accessToken, + model.accessTokenLifetime, + model.refreshToken, + model.scope, + model.customAttributes, + ); + } + + /** + * Update response when a token is generated. + */ + + updateSuccessResponse(response: Response, tokenType: BearerTokenType) { + response.body = tokenType.valueOf(); + + response.set('Cache-Control', 'no-store'); + response.set('Pragma', 'no-cache'); + } + + /** + * Update response when an error is thrown. + */ + + updateErrorResponse(response: Response, error: OAuthError) { + response.body = { + error: error.name, + error_description: error.message, + }; + + response.status = error.code; + } + + /** + * Given a grant type, check if client authentication is required. + */ + isClientAuthenticationRequired(grantType: string) { + if (Object.keys(this.requireClientAuthentication).length > 0) { + return typeof this.requireClientAuthentication[grantType] !== 'undefined' + ? this.requireClientAuthentication[grantType] + : true; + } + + return true; + } +} diff --git a/lib/interfaces/authorization-code.interface.ts b/lib/interfaces/authorization-code.interface.ts new file mode 100644 index 000000000..a1f781607 --- /dev/null +++ b/lib/interfaces/authorization-code.interface.ts @@ -0,0 +1,14 @@ +import { Client, User } from '.'; + +/** + * An interface representing the authorization code and associated data. + */ +export interface AuthorizationCode { + authorizationCode: string; + expiresAt: Date; + redirectUri: string; + scope?: string; + client: Client; + user: User; + [key: string]: any; +} diff --git a/lib/interfaces/client.interface.ts b/lib/interfaces/client.interface.ts new file mode 100644 index 000000000..dcdb074fe --- /dev/null +++ b/lib/interfaces/client.interface.ts @@ -0,0 +1,11 @@ +/** + * An interface representing the client and associated data + */ +export interface Client { + id: string; + redirectUris?: string | string[]; + grants: string | string[]; + accessTokenLifetime?: number; + refreshTokenLifetime?: number; + [key: string]: any; +} diff --git a/lib/interfaces/index.ts b/lib/interfaces/index.ts new file mode 100644 index 000000000..a5cb2aeac --- /dev/null +++ b/lib/interfaces/index.ts @@ -0,0 +1,6 @@ +export { AuthorizationCode } from './authorization-code.interface'; +export { Client } from './client.interface'; +export { Model } from './model.interface'; +export { RefreshToken } from './refresh-token.interface'; +export { Token } from './token.interface'; +export { User } from './user.interface'; diff --git a/lib/interfaces/model.interface.ts b/lib/interfaces/model.interface.ts new file mode 100644 index 000000000..14e880e67 --- /dev/null +++ b/lib/interfaces/model.interface.ts @@ -0,0 +1,175 @@ +import { AuthorizationCode, Client, RefreshToken, Token, User } from '.'; +import { Request } from '../request'; + +export interface BaseModel { + request: Request; + /** + * Invoked to generate a new access token. + * + */ + generateAccessToken?( + client: Client, + user: User, + scope: string, + ): Promise; + + /** + * Invoked to retrieve a client using a client id or a + * client id/client secret combination, depending on the grant type. + * + */ + getClient(clientId: string, clientSecret?: string): Promise; + + /** + * Invoked to save an access token and optionally a refresh token, depending on the grant type. + * + */ + saveToken(token: Token, client: Client, user: User): Promise; +} + +export interface RequestAuthenticationModel { + /** + * Invoked to retrieve an existing access token previously saved through Model#saveToken(). + * + */ + getAccessToken(accessToken: string): Promise; + + /** + * Invoked during request authentication to check if + * the provided access token was authorized the requested scopes. + * + */ + verifyScope(token: Token, scope: string): Promise; +} + +export interface AuthorizationCodeModel + extends BaseModel, + RequestAuthenticationModel { + /** + * Invoked to generate a new refresh token. + * + */ + generateRefreshToken?( + client: Client, + user: User, + scope: string, + ): Promise; + + /** + * Invoked to generate a new authorization code. + * + */ + generateAuthorizationCode?( + client: Client, + user: User, + scope: string, + ): Promise; + + /** + * Invoked to retrieve an existing authorization + * code previously saved through Model#saveAuthorizationCode(). + * + */ + getAuthorizationCode(authorizationCode: string): Promise; + + /** + * Invoked to save an authorization code. + * + */ + saveAuthorizationCode( + code: AuthorizationCode, + client: Client, + user: User, + ): Promise; + + /** + * Invoked to revoke an authorization code. + * + */ + revokeAuthorizationCode(code: AuthorizationCode): Promise; + + /** + * Invoked to check if the requested scope is + * valid for a particular client/user combination. + * + */ + validateScope?(user: User, client: Client, scope: string): Promise; +} + +export interface PasswordModel extends BaseModel, RequestAuthenticationModel { + /** + * Invoked to generate a new refresh token. + * + */ + generateRefreshToken?( + client: Client, + user: User, + scope: string, + ): Promise; + + /** + * Invoked to retrieve a user using a + * username/password combination. + * + */ + getUser(username: string, password: string): Promise; + + /** + * Invoked to check if the requested scope + * is valid for a particular client/user combination. + * + */ + validateScope?(user: User, client: Client, scope: string): Promise; +} + +export interface RefreshTokenModel + extends BaseModel, + RequestAuthenticationModel { + /** + * Invoked to generate a new refresh token. + * + */ + generateRefreshToken?( + client: Client, + user: User, + scope: string, + ): Promise; + + /** + * Invoked to retrieve an existing refresh token previously saved through Model#saveToken(). + * + */ + getRefreshToken(refreshToken: string): Promise; + + /** + * Invoked to revoke a refresh token. + * + */ + revokeToken(token: RefreshToken | Token): Promise; +} + +export interface ClientCredentialsModel + extends BaseModel, + RequestAuthenticationModel { + /** + * Invoked to retrieve the user associated with the specified client. + * + */ + getUserFromClient(client: Client): Promise; + + /** + * Invoked to check if the requested scope is valid for a particular client/user combination. + * + */ + validateScope?(user: User, client: Client, scope: string): Promise; +} + +export interface ExtensionModel extends BaseModel, RequestAuthenticationModel {} + +export interface Model + extends BaseModel, + RequestAuthenticationModel, + AuthorizationCodeModel, + PasswordModel, + RefreshTokenModel, + ClientCredentialsModel {} diff --git a/lib/interfaces/refresh-token.interface.ts b/lib/interfaces/refresh-token.interface.ts new file mode 100644 index 000000000..71801f87d --- /dev/null +++ b/lib/interfaces/refresh-token.interface.ts @@ -0,0 +1,13 @@ +import { Client, User } from '.'; + +/** + * An interface representing the refresh token and associated data. + */ +export interface RefreshToken { + refreshToken: string; + refreshTokenExpiresAt?: Date; + scope?: string; + client: Client; + user: User; + [key: string]: any; +} diff --git a/lib/interfaces/token.interface.ts b/lib/interfaces/token.interface.ts new file mode 100644 index 000000000..ec696e687 --- /dev/null +++ b/lib/interfaces/token.interface.ts @@ -0,0 +1,15 @@ +import { Client, User } from '.'; + +/** + * An interface representing the token(s) and associated data. + */ +export interface Token { + accessToken: string; + accessTokenExpiresAt?: Date; + refreshToken?: string; + refreshTokenExpiresAt?: Date; + scope?: string; + client: Client; + user: User; + [key: string]: any; +} diff --git a/lib/interfaces/user.interface.ts b/lib/interfaces/user.interface.ts new file mode 100644 index 000000000..dc5afd5b0 --- /dev/null +++ b/lib/interfaces/user.interface.ts @@ -0,0 +1,7 @@ +/** + * An interface representing the user. + * A user object is completely transparent to oauth2-server and is simply used as input to model functions. + */ +export interface User { + [key: string]: any; +} diff --git a/lib/models/index.ts b/lib/models/index.ts new file mode 100644 index 000000000..34c69db1f --- /dev/null +++ b/lib/models/index.ts @@ -0,0 +1 @@ +export { TokenModel } from './token-model'; diff --git a/lib/models/token-model.js b/lib/models/token-model.js deleted file mode 100644 index c6bc3f8d4..000000000 --- a/lib/models/token-model.js +++ /dev/null @@ -1,65 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var InvalidArgumentError = require('../errors/invalid-argument-error'); - -/** - * Constructor. - */ - -var modelAttributes = ['accessToken', 'accessTokenExpiresAt', 'refreshToken', 'refreshTokenExpiresAt', 'scope', 'client', 'user']; - -function TokenModel(data, options) { - data = data || {}; - - if (!data.accessToken) { - throw new InvalidArgumentError('Missing parameter: `accessToken`'); - } - - if (!data.client) { - throw new InvalidArgumentError('Missing parameter: `client`'); - } - - if (!data.user) { - throw new InvalidArgumentError('Missing parameter: `user`'); - } - - if (data.accessTokenExpiresAt && !(data.accessTokenExpiresAt instanceof Date)) { - throw new InvalidArgumentError('Invalid parameter: `accessTokenExpiresAt`'); - } - - if (data.refreshTokenExpiresAt && !(data.refreshTokenExpiresAt instanceof Date)) { - throw new InvalidArgumentError('Invalid parameter: `refreshTokenExpiresAt`'); - } - - this.accessToken = data.accessToken; - this.accessTokenExpiresAt = data.accessTokenExpiresAt; - this.client = data.client; - this.refreshToken = data.refreshToken; - this.refreshTokenExpiresAt = data.refreshTokenExpiresAt; - this.scope = data.scope; - this.user = data.user; - - if (options && options.allowExtendedTokenAttributes) { - this.customAttributes = {}; - - for (var key in data) { - if (data.hasOwnProperty(key) && (modelAttributes.indexOf(key) < 0)) { - this.customAttributes[key] = data[key]; - } - } - } - - if(this.accessTokenExpiresAt) { - this.accessTokenLifetime = Math.floor((this.accessTokenExpiresAt - new Date()) / 1000); - } -} - -/** - * Export constructor. - */ - -module.exports = TokenModel; diff --git a/lib/models/token-model.ts b/lib/models/token-model.ts new file mode 100755 index 000000000..e5104901f --- /dev/null +++ b/lib/models/token-model.ts @@ -0,0 +1,82 @@ +import { MILLISECONDS_PER_SECOND } from '../constants'; +import { InvalidArgumentError } from '../errors'; +import { Client, Token, User } from '../interfaces'; +import { hasOwnProperty } from '../utils/fn'; + +const modelAttributes = [ + 'accessToken', + 'accessTokenExpiresAt', + 'client', + 'refreshToken', + 'refreshTokenExpiresAt', + 'scope', + 'user', +]; + +export class TokenModel implements Token { + accessToken: string; + accessTokenExpiresAt?: Date; + refreshToken?: string; + refreshTokenExpiresAt?: Date; + scope?: string; + client: Client; + user: User; + customAttributes: {}; + accessTokenLifetime: number; + constructor(data: any = {}, options: any = {}) { + if (!data.accessToken) { + throw new InvalidArgumentError('Missing parameter: `accessToken`'); + } + + if (!data.client) { + throw new InvalidArgumentError('Missing parameter: `client`'); + } + + if (!data.user) { + throw new InvalidArgumentError('Missing parameter: `user`'); + } + + if ( + data.accessTokenExpiresAt && + !(data.accessTokenExpiresAt instanceof Date) + ) { + throw new InvalidArgumentError( + 'Invalid parameter: `accessTokenExpiresAt`', + ); + } + + if ( + data.refreshTokenExpiresAt && + !(data.refreshTokenExpiresAt instanceof Date) + ) { + throw new InvalidArgumentError( + 'Invalid parameter: `refreshTokenExpiresAt`', + ); + } + + this.accessToken = data.accessToken; + this.accessTokenExpiresAt = data.accessTokenExpiresAt; + this.client = data.client; + this.refreshToken = data.refreshToken; + this.refreshTokenExpiresAt = data.refreshTokenExpiresAt; + this.scope = data.scope; + this.user = data.user; + + if (options && options.allowExtendedTokenAttributes) { + this.customAttributes = {}; + + for (const key of Object.keys(data)) { + if (hasOwnProperty(data, key) && modelAttributes.indexOf(key) < 0) { + this.customAttributes[key] = data[key]; + } + } + } + + if (this.accessTokenExpiresAt) { + this.accessTokenLifetime = Math.floor( + (this.accessTokenExpiresAt.getTime() - new Date().getTime()) / + MILLISECONDS_PER_SECOND, + ); + } + } +} diff --git a/lib/request.ts b/lib/request.ts new file mode 100755 index 000000000..5bfdfe467 --- /dev/null +++ b/lib/request.ts @@ -0,0 +1,77 @@ +import * as typeis from 'type-is'; +import { InvalidArgumentError } from './errors'; +import { hasOwnProperty } from './utils/fn'; + +export class Request { + body: any; + headers: any; + method: string; + query: any; + constructor( + options: { + body: any; + headers: any; + method: string; + query: any; + [key: string]: any; + } = {} as any, + ) { + if (!options.headers) { + throw new InvalidArgumentError('Missing parameter: `headers`'); + } + + if (!options.method) { + throw new InvalidArgumentError('Missing parameter: `method`'); + } + + if (typeof options.method !== 'string') { + throw new InvalidArgumentError('Invalid parameter: `method`'); + } + + if (!options.query) { + throw new InvalidArgumentError('Missing parameter: `query`'); + } + + this.body = options.body || {}; + this.headers = {}; + this.method = options.method.toUpperCase(); + this.query = options.query; + + // Store the headers in lower case. + for (const field of Object.keys(options.headers)) { + if (hasOwnProperty(options.headers, field)) { + this.headers[field.toLowerCase()] = options.headers[field]; + } + } + + // Store additional properties of the request object passed in + for (const property of Object.keys(options)) { + if (hasOwnProperty(options, property) && !this[property]) { + this[property] = options[property]; + } + } + } + + /** + * Get a request header. + */ + + get(field: string) { + return this.headers[field.toLowerCase()]; + } + + /** + * Check if the content-type matches any of the given mime type. + */ + public is(args: string[]): string | false; + public is(...args: string[]): string | false; + + is(...args) { + let types = args; + if (Array.isArray(types[0])) { + types = types[0]; + } + + return typeis(this as any, types) || false; + } +} diff --git a/lib/response-types/code-response-type.js b/lib/response-types/code-response-type.js deleted file mode 100644 index 6eaf23a89..000000000 --- a/lib/response-types/code-response-type.js +++ /dev/null @@ -1,43 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var InvalidArgumentError = require('../errors/invalid-argument-error'); -var url = require('url'); - -/** - * Constructor. - */ - -function CodeResponseType(code) { - if (!code) { - throw new InvalidArgumentError('Missing parameter: `code`'); - } - - this.code = code; -} - -/** - * Build redirect uri. - */ - -CodeResponseType.prototype.buildRedirectUri = function(redirectUri) { - if (!redirectUri) { - throw new InvalidArgumentError('Missing parameter: `redirectUri`'); - } - - var uri = url.parse(redirectUri, true); - - uri.query.code = this.code; - uri.search = null; - - return uri; -}; - -/** - * Export constructor. - */ - -module.exports = CodeResponseType; diff --git a/lib/response-types/code-response-type.ts b/lib/response-types/code-response-type.ts new file mode 100755 index 000000000..0d1b93e31 --- /dev/null +++ b/lib/response-types/code-response-type.ts @@ -0,0 +1,165 @@ +import { MILLISECONDS_PER_SECOND } from '../constants'; +import { InvalidArgumentError } from '../errors'; +import { AuthorizationCode, Client, Model, User } from '../interfaces'; +import { Request } from '../request'; +import * as tokenUtil from '../utils/token-util'; +export class CodeResponseType { + code: any; + authorizationCodeLifetime: number; + model: Model; + constructor(options: any = {}) { + if (!options.authorizationCodeLifetime) { + throw new InvalidArgumentError( + 'Missing parameter: `authorizationCodeLifetime`', + ); + } + + if (!options.model) { + throw new InvalidArgumentError('Missing parameter: `model`'); + } + + if (!options.model.saveAuthorizationCode) { + throw new InvalidArgumentError( + 'Invalid argument: model does not implement `saveAuthorizationCode()`', + ); + } + + this.code = undefined; + this.authorizationCodeLifetime = options.authorizationCodeLifetime; + this.model = options.model; + } + + /** + * Handle code response type. + */ + + async handle( + request: Request, + client: Client, + user: User, + uri: string, + scope: string, + ) { + if (!request) { + throw new InvalidArgumentError('Missing parameter: `request`'); + } + + if (!client) { + throw new InvalidArgumentError('Missing parameter: `client`'); + } + + if (!user) { + throw new InvalidArgumentError('Missing parameter: `user`'); + } + + if (!uri) { + throw new InvalidArgumentError('Missing parameter: `uri`'); + } + + const authorizationCode = await this.generateAuthorizationCode( + client, + user, + scope, + ); + const expiresAt = this.getAuthorizationCodeExpiresAt(client); + + const code = await this.saveAuthorizationCode( + authorizationCode, + expiresAt, + scope, + client, + uri, + user, + ); + this.code = code.authorizationCode; + + return code; + } + + /** + * Get authorization code expiration date. + */ + + getAuthorizationCodeExpiresAt(client: Client) { + const authorizationCodeLifetime = this.getAuthorizationCodeLifetime(client); + + return new Date( + Date.now() + authorizationCodeLifetime * MILLISECONDS_PER_SECOND, + ); + } + + /** + * Get authorization code lifetime. + */ + + getAuthorizationCodeLifetime(client: Client) { + return client.authorizationCodeLifetime || this.authorizationCodeLifetime; + } + + /** + * Save authorization code. + */ + + async saveAuthorizationCode( + authorizationCode: string, + expiresAt: Date, + scope: string, + client: Client, + redirectUri: any, + user: User, + ) { + const code = { + authorizationCode, + expiresAt, + redirectUri, + scope, + } as AuthorizationCode; + + return this.model.saveAuthorizationCode(code, client, user); + } + + /** + * Generate authorization code. + */ + + async generateAuthorizationCode(client: Client, user: User, scope: string) { + if (this.model.generateAuthorizationCode) { + return this.model.generateAuthorizationCode(client, user, scope); + } + + return tokenUtil.GenerateRandomToken(); + } + + /** + * Build redirect uri. + */ + + buildRedirectUri(redirectUri: any) { + if (!redirectUri) { + throw new InvalidArgumentError('Missing parameter: `redirectUri`'); + } + + redirectUri.search = undefined; + + return this.setRedirectUriParam(redirectUri, 'code', this.code); + } + + /** + * Set redirect uri parameter. + */ + + setRedirectUriParam(redirectUri: any, key: string, value: string) { + if (!redirectUri) { + throw new InvalidArgumentError('Missing parameter: `redirectUri`'); + } + + if (!key) { + throw new InvalidArgumentError('Missing parameter: `key`'); + } + + redirectUri.query = redirectUri.query || {}; + redirectUri.query[key] = value; + + return redirectUri; + } +} diff --git a/lib/response-types/index.ts b/lib/response-types/index.ts new file mode 100644 index 000000000..9866921eb --- /dev/null +++ b/lib/response-types/index.ts @@ -0,0 +1,2 @@ +export { CodeResponseType } from './code-response-type'; +export { TokenResponseType } from './token-response-type'; diff --git a/lib/response-types/token-response-type.js b/lib/response-types/token-response-type.js deleted file mode 100644 index 2637f64cd..000000000 --- a/lib/response-types/token-response-type.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var ServerError = require('../errors/server-error'); - -/** - * Constructor. - */ - -function TokenResponseType() { - throw new ServerError('Not implemented.'); -} - -/** - * Export constructor. - */ - -module.exports = TokenResponseType; diff --git a/lib/response-types/token-response-type.ts b/lib/response-types/token-response-type.ts new file mode 100755 index 000000000..8901a3228 --- /dev/null +++ b/lib/response-types/token-response-type.ts @@ -0,0 +1,97 @@ +import { InvalidArgumentError } from '../errors'; +import { ImplicitGrantType } from '../grant-types'; +import { Client, Model, User } from '../interfaces'; +import { Request } from '../request'; + +export class TokenResponseType { + accessToken: string; + accessTokenLifetime: number; + model: Model; + constructor(options: any = {}) { + if (!options.accessTokenLifetime) { + throw new InvalidArgumentError( + 'Missing parameter: `accessTokenLifetime`', + ); + } + + this.accessToken = undefined; + this.accessTokenLifetime = options.accessTokenLifetime; + this.model = options.model; + } + + /** + * Handle token response type. + */ + + async handle( + request: Request, + client: Client, + user: User, + uri: string, + scope: string, + ) { + if (!request) { + throw new InvalidArgumentError('Missing parameter: `request`'); + } + + if (!client) { + throw new InvalidArgumentError('Missing parameter: `client`'); + } + + const accessTokenLifetime = this.getAccessTokenLifetime(client); + + const options = { + user, + scope, + model: this.model, + accessTokenLifetime, + }; + + const grantType = new ImplicitGrantType(options); + const token = await grantType.handle(request, client); + this.accessToken = token.accessToken; + + return token; + } + + /** + * Get access token lifetime. + */ + + getAccessTokenLifetime(client: Client) { + return client.accessTokenLifetime || this.accessTokenLifetime; + } + + /** + * Build redirect uri. + */ + + buildRedirectUri(redirectUri: any) { + return this.setRedirectUriParam( + redirectUri, + 'access_token', + this.accessToken, + ); + } + + /** + * Set redirect uri parameter. + */ + + setRedirectUriParam(redirectUri: any, key: string, value: any) { + if (!redirectUri) { + throw new InvalidArgumentError('Missing parameter: `redirectUri`'); + } + + if (!key) { + throw new InvalidArgumentError('Missing parameter: `key`'); + } + + redirectUri.hash = redirectUri.hash || ''; + redirectUri.hash += `${ + redirectUri.hash ? '&' : '' + }${key}=${encodeURIComponent(value)}`; + + return redirectUri; + } +} diff --git a/lib/response.ts b/lib/response.ts new file mode 100755 index 000000000..3128f10a0 --- /dev/null +++ b/lib/response.ts @@ -0,0 +1,51 @@ +import { hasOwnProperty } from './utils/fn'; + +export class Response { + body: any; + headers: any; + status: number; + constructor(options: any = {}) { + this.body = options.body || {}; + this.headers = {}; + this.status = 200; // OK + + // Store the headers in lower case. + for (const field of Object.keys(options.headers || {})) { + if (hasOwnProperty(options.headers, field)) { + this.headers[field.toLowerCase()] = options.headers[field]; + } + } + + // Store additional properties of the response object passed in. + for (const property of Object.keys(options)) { + if (hasOwnProperty(options, property) && !this[property]) { + this[property] = options[property]; + } + } + } + + /** + * Get a response header. + */ + + get(field: string) { + return this.headers[field.toLowerCase()]; + } + + /** + * Redirect response. + */ + + redirect(url: string) { + this.set('Location', url); + this.status = 302; // Found + } + + /** + * Set a response header. + */ + + set(field: string, value: string) { + this.headers[field.toLowerCase()] = value; + } +} diff --git a/lib/server.js b/lib/server.js deleted file mode 100644 index fba9ccf81..000000000 --- a/lib/server.js +++ /dev/null @@ -1,83 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var _ = require('lodash'); -var AuthenticateHandler = require('./handlers/authenticate-handler'); -var AuthorizeHandler = require('./handlers/authorize-handler'); -var InvalidArgumentError = require('./errors/invalid-argument-error'); -var TokenHandler = require('./handlers/token-handler'); - -/** - * Constructor. - */ - -function OAuth2Server(options) { - options = options || {}; - - if (!options.model) { - throw new InvalidArgumentError('Missing parameter: `model`'); - } - - this.options = options; -} - -/** - * Authenticate a token. - */ - -OAuth2Server.prototype.authenticate = function(request, response, options, callback) { - if (typeof options === 'string') { - options = {scope: options}; - } - - options = _.assign({ - addAcceptedScopesHeader: true, - addAuthorizedScopesHeader: true, - allowBearerTokensInQueryString: false - }, this.options, options); - - return new AuthenticateHandler(options) - .handle(request, response) - .nodeify(callback); -}; - -/** - * Authorize a request. - */ - -OAuth2Server.prototype.authorize = function(request, response, options, callback) { - options = _.assign({ - allowEmptyState: false, - authorizationCodeLifetime: 5 * 60 // 5 minutes. - }, this.options, options); - - return new AuthorizeHandler(options) - .handle(request, response) - .nodeify(callback); -}; - -/** - * Create a token. - */ - -OAuth2Server.prototype.token = function(request, response, options, callback) { - options = _.assign({ - accessTokenLifetime: 60 * 60, // 1 hour. - refreshTokenLifetime: 60 * 60 * 24 * 14, // 2 weeks. - allowExtendedTokenAttributes: false, - requireClientAuthentication: {} // defaults to true for all grant types - }, this.options, options); - - return new TokenHandler(options) - .handle(request, response) - .nodeify(callback); -}; - -/** - * Export constructor. - */ - -module.exports = OAuth2Server; diff --git a/lib/server.ts b/lib/server.ts new file mode 100755 index 000000000..3e81e441d --- /dev/null +++ b/lib/server.ts @@ -0,0 +1,100 @@ +import { HOUR, MINUTE, SECOND, WEEK } from './constants'; +import { InvalidArgumentError } from './errors'; +import { + AuthenticateHandler, + AuthorizeHandler, + RevokeHandler, + TokenHandler, +} from './handlers'; +import { Request } from './request'; +import { Response } from './response'; + +export class OAuth2Server { + options: any; + constructor(options: any = {}) { + if (!options.model) { + throw new InvalidArgumentError('Missing parameter: `model`'); + } + + this.options = options; + } + + /** + * Authenticate a token. + */ + authenticate( + request: Request, + response?: Response, + scope?: string, + ): Promise; + authenticate( + request: Request, + response?: Response, + // tslint:disable-next-line:unified-signatures + options?: any, + ): Promise; + + async authenticate( + request: Request, + response?: Response, + options?: string | any, + ) { + let opt = options; + if (typeof opt === 'string') { + opt = { scope: opt }; + } + + opt = { + addAcceptedScopesHeader: true, + addAuthorizedScopesHeader: true, + allowBearerTokensInQueryString: false, + ...this.options, + ...opt, + }; + + return new AuthenticateHandler(opt).handle(request, response); + } + + /** + * Authorize a request. + */ + + async authorize(request: Request, response: Response, options?: any) { + const opts = { + allowEmptyState: false, + accessTokenLifetime: HOUR / SECOND, + authorizationCodeLifetime: (MINUTE * 5) / SECOND, + ...this.options, + ...options, + }; + + return new AuthorizeHandler(opts).handle(request, response); + } + + /** + * Create a token. + */ + + async token(request: Request, response: Response, options?: any) { + const opts = { + accessTokenLifetime: HOUR / SECOND, // 1 hour in seconds. + refreshTokenLifetime: (WEEK * 2) / SECOND, // 2 weeks. + allowExtendedTokenAttributes: false, + requireClientAuthentication: {}, + ...this.options, + ...options, + }; + + return new TokenHandler(opts).handle(request, response); + } + + /** + * Revoke a token. + */ + + async revoke(request: Request, response: Response, options: any) { + const opt = { ...this.options, ...options }; + + return new RevokeHandler(opt).handle(request, response); + } +} diff --git a/lib/token-types/bearer-token-type.js b/lib/token-types/bearer-token-type.js deleted file mode 100644 index 9124cb2e8..000000000 --- a/lib/token-types/bearer-token-type.js +++ /dev/null @@ -1,62 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var InvalidArgumentError = require('../errors/invalid-argument-error'); - -/** - * Constructor. - */ - -function BearerTokenType(accessToken, accessTokenLifetime, refreshToken, scope, customAttributes) { - if (!accessToken) { - throw new InvalidArgumentError('Missing parameter: `accessToken`'); - } - - this.accessToken = accessToken; - this.accessTokenLifetime = accessTokenLifetime; - this.refreshToken = refreshToken; - this.scope = scope; - - if (customAttributes) { - this.customAttributes = customAttributes; - } -} - -/** - * Retrieve the value representation. - */ - -BearerTokenType.prototype.valueOf = function() { - var object = { - access_token: this.accessToken, - token_type: 'Bearer' - }; - - if (this.accessTokenLifetime) { - object.expires_in = this.accessTokenLifetime; - } - - if (this.refreshToken) { - object.refresh_token = this.refreshToken; - } - - if (this.scope) { - object.scope = this.scope; - } - - for (var key in this.customAttributes) { - if (this.customAttributes.hasOwnProperty(key)) { - object[key] = this.customAttributes[key]; - } - } - return object; -}; - -/** - * Export constructor. - */ - -module.exports = BearerTokenType; diff --git a/lib/token-types/bearer-token-type.ts b/lib/token-types/bearer-token-type.ts new file mode 100755 index 000000000..dd08c4e72 --- /dev/null +++ b/lib/token-types/bearer-token-type.ts @@ -0,0 +1,61 @@ +import { InvalidArgumentError } from '../errors'; +import { hasOwnProperty } from '../utils/fn'; + +export class BearerTokenType { + accessToken: string; + accessTokenLifetime: number; + refreshToken: string; + scope: string; + customAttributes: any; + constructor( + accessToken: string, + accessTokenLifetime: number, + refreshToken: string, + scope: string, + customAttributes: any, + ) { + if (!accessToken) { + throw new InvalidArgumentError('Missing parameter: `accessToken`'); + } + + this.accessToken = accessToken; + this.accessTokenLifetime = accessTokenLifetime; + this.refreshToken = refreshToken; + this.scope = scope; + + if (customAttributes) { + this.customAttributes = customAttributes; + } + } + + /** + * Retrieve the value representation. + */ + + valueOf() { + const object: any = { + access_token: this.accessToken, + token_type: 'Bearer', + }; + + if (this.accessTokenLifetime) { + object.expires_in = this.accessTokenLifetime; + } + + if (this.refreshToken) { + object.refresh_token = this.refreshToken; + } + + if (this.scope) { + object.scope = this.scope; + } + + for (const key of Object.keys(this.customAttributes || {})) { + if (hasOwnProperty(this.customAttributes, key)) { + object[key] = this.customAttributes[key]; + } + } + + return object; + } +} diff --git a/lib/token-types/index.ts b/lib/token-types/index.ts new file mode 100644 index 000000000..7fa71bce4 --- /dev/null +++ b/lib/token-types/index.ts @@ -0,0 +1,2 @@ +export { BearerTokenType } from './bearer-token-type'; +export { MacTokenType } from './mac-token-type'; diff --git a/lib/token-types/mac-token-type.js b/lib/token-types/mac-token-type.js deleted file mode 100644 index 9fdc600c6..000000000 --- a/lib/token-types/mac-token-type.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var ServerError = require('../errors/server-error'); - -/** - * Constructor. - */ - -function MacTokenType() { - throw new ServerError('Not implemented.'); -} - -/** - * Export constructor. - */ - -module.exports = MacTokenType; diff --git a/lib/token-types/mac-token-type.ts b/lib/token-types/mac-token-type.ts new file mode 100755 index 000000000..8210f7c0d --- /dev/null +++ b/lib/token-types/mac-token-type.ts @@ -0,0 +1,8 @@ +import { ServerError } from '../errors'; + +// tslint:disable-next-line:no-unnecessary-class +export class MacTokenType { + constructor() { + throw new ServerError('Not implemented.'); + } +} diff --git a/lib/utils/fn.ts b/lib/utils/fn.ts new file mode 100644 index 000000000..ac5200c4d --- /dev/null +++ b/lib/utils/fn.ts @@ -0,0 +1,20 @@ +const identity = (v: any) => v; + +const reverser = (promise: Promise) => + promise.then(v => Promise.reject(v), identity); + +export const oneSuccess = (promises: Array>) => + Promise.all(promises.map(reverser)).then( + e => Promise.reject(AggregateError.from(e)), + identity, + ); + +export const hasOwnProperty = (o: any, k: string) => + Object.prototype.hasOwnProperty.call(o, k); + +export class AggregateError extends Array implements Error { + name = 'AggregateError'; + get message() { + return this.map(e => e.message).join('\n'); + } +} diff --git a/lib/utils/token-util.js b/lib/utils/token-util.js deleted file mode 100644 index 0f73746bb..000000000 --- a/lib/utils/token-util.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var crypto = require('crypto'); -var randomBytes = require('bluebird').promisify(require('crypto').randomBytes); - -/** - * Export `TokenUtil`. - */ - -module.exports = { - - /** - * Generate random token. - */ - - generateRandomToken: function() { - return randomBytes(256).then(function(buffer) { - return crypto - .createHash('sha1') - .update(buffer) - .digest('hex'); - }); - } - -}; diff --git a/lib/utils/token-util.ts b/lib/utils/token-util.ts new file mode 100755 index 000000000..9e9270ed0 --- /dev/null +++ b/lib/utils/token-util.ts @@ -0,0 +1,16 @@ +import { createHash, randomBytes } from 'crypto'; +import { promisify } from 'util'; +const randomBytesPromise = promisify(randomBytes); + +/** + * Generate random token. + */ + +export const GenerateRandomToken = async () => { + const bytesSize = 256; + const buffer = await randomBytesPromise(bytesSize); + + return createHash('sha1') + .update(buffer) + .digest('hex'); +}; diff --git a/lib/validator/is.js b/lib/validator/is.js deleted file mode 100644 index 07af6cb64..000000000 --- a/lib/validator/is.js +++ /dev/null @@ -1,81 +0,0 @@ -'use strict'; - -/** - * Validation rules. - */ - -var rules = { - NCHAR: /^[\u002D|\u002E|\u005F|\w]+$/, - NQCHAR: /^[\u0021|\u0023-\u005B|\u005D-\u007E]+$/, - NQSCHAR: /^[\u0020-\u0021|\u0023-\u005B|\u005D-\u007E]+$/, - UNICODECHARNOCRLF: /^[\u0009|\u0020-\u007E|\u0080-\uD7FF|\uE000-\uFFFD|\u10000-\u10FFFF]+$/, - URI: /^[a-zA-Z][a-zA-Z0-9+.-]+:/, - VSCHAR: /^[\u0020-\u007E]+$/ -}; - -/** - * Export validation functions. - */ - -module.exports = { - - /** - * Validate if a value matches a unicode character. - * - * @see https://tools.ietf.org/html/rfc6749#appendix-A - */ - - nchar: function(value) { - return rules.NCHAR.test(value); - }, - - /** - * Validate if a value matches a unicode character, including exclamation marks. - * - * @see https://tools.ietf.org/html/rfc6749#appendix-A - */ - - nqchar: function(value) { - return rules.NQCHAR.test(value); - }, - - /** - * Validate if a value matches a unicode character, including exclamation marks and spaces. - * - * @see https://tools.ietf.org/html/rfc6749#appendix-A - */ - - nqschar: function(value) { - return rules.NQSCHAR.test(value); - }, - - /** - * Validate if a value matches a unicode character excluding the carriage - * return and linefeed characters. - * - * @see https://tools.ietf.org/html/rfc6749#appendix-A - */ - - uchar: function(value) { - return rules.UNICODECHARNOCRLF.test(value); - }, - - /** - * Validate if a value matches generic URIs. - * - * @see http://tools.ietf.org/html/rfc3986#section-3 - */ - uri: function(value) { - return rules.URI.test(value); - }, - - /** - * Validate if a value matches against the printable set of unicode characters. - * - * @see https://tools.ietf.org/html/rfc6749#appendix-A - */ - - vschar: function(value) { - return rules.VSCHAR.test(value); - } -}; diff --git a/lib/validator/is.ts b/lib/validator/is.ts new file mode 100755 index 000000000..d9475878a --- /dev/null +++ b/lib/validator/is.ts @@ -0,0 +1,64 @@ +/** + * Validation rules. + */ + +const Rules = { + NCHAR: /^[\u002D|\u002E|\u005F|\w]+$/, + NQCHAR: /^[\u0021|\u0023-\u005B|\u005D-\u007E]+$/, + NQSCHAR: /^[\u0020-\u0021|\u0023-\u005B|\u005D-\u007E]+$/, + UNICODECHARNOCRLF: /^[\u0009|\u0020-\u007E|\u0080-\uD7FF|\uE000-\uFFFD|\u10000-\u10FFFF]+$/, + URI: /^[a-zA-Z][a-zA-Z0-9+.-]+:/, + VSCHAR: /^[\u0020-\u007E]+$/, +}; + +/** + * Export validation functions. + */ + +/** + * Validate if a value matches a unicode character. + * + * @see https://tools.ietf.org/html/rfc6749#appendix-A + */ + +export const nchar = (value: string) => Rules.NCHAR.test(value); + +/** + * Validate if a value matches a unicode character, including exclamation marks. + * + * @see https://tools.ietf.org/html/rfc6749#appendix-A + */ + +export const nqchar = (value: string) => Rules.NQCHAR.test(value); + +/** + * Validate if a value matches a unicode character, including exclamation marks and spaces. + * + * @see https://tools.ietf.org/html/rfc6749#appendix-A + */ + +export const nqschar = (value: string) => Rules.NQSCHAR.test(value); + +/** + * Validate if a value matches a unicode character excluding the carriage + * and linefeed characters. + * + * @see https://tools.ietf.org/html/rfc6749#appendix-A + */ + +export const uchar = (value: string) => Rules.UNICODECHARNOCRLF.test(value); + +/** + * Validate if a value matches generic URIs. + * + * @see http://tools.ietf.org/html/rfc3986#section-3 + */ +export const uri = (value: string) => Rules.URI.test(value); + +/** + * Validate if a value matches against the printable set of unicode characters. + * + * @see https://tools.ietf.org/html/rfc6749#appendix-A + */ + +export const vschar = (value: string) => Rules.VSCHAR.test(value); diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 486ec72c6..000000000 --- a/package-lock.json +++ /dev/null @@ -1,640 +0,0 @@ -{ - "name": "oauth2-server", - "version": "3.1.1", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@sinonjs/commons": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.0.tgz", - "integrity": "sha512-wEj54PfsZ5jGSwMX68G8ZXFawcSglQSXqCftWX3ec8MDUzQdHgcKvw97awHbY0efQEL5iKUOAmmVtoYgmrSG4Q==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@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==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^3.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==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.3.0", - "array-from": "^2.1.1", - "lodash": "^4.17.15" - } - }, - "@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 - }, - "array-from": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", - "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", - "dev": true - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "basic-auth": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", - "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", - "requires": { - "safe-buffer": "5.1.2" - } - }, - "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, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "cli": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz", - "integrity": "sha1-IoF1NPJL+klQw01TLUjsvGIbjBQ=", - "dev": true, - "requires": { - "exit": "0.1.2", - "glob": "^7.1.1" - } - }, - "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": { - "bluebird": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", - "integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=" - } - } - }, - "co-use": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/co-use/-/co-use-1.1.0.tgz", - "integrity": "sha1-xrs83xDLc17Kqdru2kbXJclKTmI=" - }, - "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 - }, - "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 - }, - "console-browserify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", - "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", - "dev": true, - "requires": { - "date-now": "^0.1.4" - } - }, - "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=", - "dev": true - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "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, - "requires": { - "domelementtype": "^2.0.1", - "entities": "^2.0.0" - }, - "dependencies": { - "domelementtype": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", - "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==", - "dev": true - }, - "entities": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", - "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==", - "dev": true - } - } - }, - "domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", - "dev": true - }, - "domhandler": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", - "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=", - "dev": true, - "requires": { - "domelementtype": "1" - } - }, - "domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", - "dev": true, - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "entities": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", - "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=", - "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 - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "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 - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "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" - } - }, - "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 - }, - "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", - "dev": true - }, - "htmlparser2": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", - "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", - "dev": true, - "requires": { - "domelementtype": "1", - "domhandler": "2.3", - "domutils": "1.5", - "entities": "1.0", - "readable-stream": "1.1" - } - }, - "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-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=", - "dev": true - }, - "jshint": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.12.0.tgz", - "integrity": "sha512-TwuuaUDmra0JMkuqvqy+WGo2xGHSNjv1BA1nTIgtH2K5z1jHuAEeAgp7laaR+hLRmajRjcrM71+vByBDanCyYA==", - "dev": true, - "requires": { - "cli": "~1.0.0", - "console-browserify": "1.1.x", - "exit": "0.1.x", - "htmlparser2": "3.8.x", - "lodash": "~4.17.19", - "minimatch": "~3.0.2", - "shelljs": "0.3.x", - "strip-json-comments": "1.0.x" - } - }, - "just-extend": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.0.tgz", - "integrity": "sha512-ApcjaOdVTJ7y4r08xI5wIqpvwS48Q0PBG4DJROcEkH1f8MdAiNFyFxz3xoL0LWAVwjrwPYZdVHHxhRHcx/uGLA==", - "dev": true - }, - "lodash": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" - }, - "lolex": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.2.0.tgz", - "integrity": "sha512-gKO5uExCXvSm6zbF562EvM+rd1kQDnB9AZBbiQVzf1ZmdDpxUSvpnAaVOP83N/31mRK8Ml8/VE8DMvsAZQ+7wg==", - "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==", - "requires": { - "mime-db": "1.44.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": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha1-bYrlCPWRZ/lA8rWzxKYSrlDJCuY=", - "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" - } - } - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "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==", - "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" - } - } - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "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=", - "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" - } - }, - "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": { - "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=" - } - } - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "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==" - }, - "shelljs": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", - "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=", - "dev": true - }, - "should": { - "version": "13.2.3", - "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", - "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", - "dev": true, - "requires": { - "should-equal": "^2.0.0", - "should-format": "^3.0.3", - "should-type": "^1.4.0", - "should-type-adaptors": "^1.0.1", - "should-util": "^1.0.0" - } - }, - "should-equal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", - "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", - "dev": true, - "requires": { - "should-type": "^1.4.0" - } - }, - "should-format": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", - "integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=", - "dev": true, - "requires": { - "should-type": "^1.3.0", - "should-type-adaptors": "^1.0.1" - } - }, - "should-type": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", - "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=", - "dev": true - }, - "should-type-adaptors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", - "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", - "dev": true, - "requires": { - "should-type": "^1.3.0", - "should-util": "^1.0.0" - } - }, - "should-util": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz", - "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==", - "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" - } - } - } - }, - "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=", - "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=", - "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==", - "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==", - "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": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - } - } -} diff --git a/package.json b/package.json index 8f5a739de..85480d67a 100644 --- a/package.json +++ b/package.json @@ -1,65 +1,60 @@ { - "name": "oauth2-server", + "name": "oauth2-server-ts", "description": "Complete, framework-agnostic, compliant and well tested module for implementing an OAuth2 Server in node.js", - "version": "3.1.1", + "version": "6.0.0+alpha", "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" - } - ], - "main": "index.js", - "dependencies": { - "basic-auth": "2.0.1", - "bluebird": "3.7.2", - "lodash": "4.17.19", - "promisify-any": "2.0.1", - "statuses": "1.5.0", - "type-is": "1.6.18" - }, - "devDependencies": { - "jshint": "2.12.0", - "mocha": "5.2.0", - "should": "13.2.3", - "sinon": "7.5.0" - }, - "license": "MIT", + "main": "index.ts", "engines": { - "node": ">=4.0" + "node": ">=8.10" }, + "license": "MIT", "scripts": { - "pretest": "./node_modules/.bin/jshint --config ./.jshintrc lib test", - "test": "NODE_ENV=test ./node_modules/.bin/mocha 'test/**/*_test.js'", - "test-debug": "NODE_ENV=test ./node_modules/.bin/mocha --inspect --debug-brk 'test/**/*_test.js'" + "lint": "tslint -p tsconfig.build.json -c tslint.json", + "lint:all": "tslint -p tsconfig.json -c tslint.json", + "build:clean": "npx shx rm -rf ./dist", + "build": "npx npm-run-all build:clean build:test:*", + "build:test:src": "npx tsc -p tsconfig.json", + "build:test:package": "node ./scripts/build-prod.js", + "build:prod": "npx npm-run-all build:clean build:prod:*", + "build:prod:src": "npx tsc -p tsconfig.build.json", + "build:prod:package": "node ./scripts/build-prod.js", + "test": "npx mocha 'test/**/*.spec.ts' --config 'test/.mocharc.jsonc'" }, "repository": { - "type": "git", - "url": "https://github.com/oauthjs/node-oauth2-server.git" + "url": "https://github.com/oauthjs/node-oauth2-server.git", + "type": "git" + }, + "devDependencies": { + "@types/mocha": "^5.2.7", + "@types/sinon": "^7.5.1", + "@typescript-eslint/eslint-plugin": "^4.2.0", + "@typescript-eslint/parser": "4.2.0", + "eslint-config-prettier": "^6.12.0", + "eslint-plugin-import": "^2.22.0", + "mocha": "^6.2.2", + "npm-run-all": "^4.1.5", + "should": "^13.2.3", + "shx": "^0.3.2", + "sinon": "^7.5.0", + "ts-node": "^8.5.4", + "tslint": "^5.20.1", + "typescript": "^3.7.2" + }, + "dependencies": { + "@types/basic-auth": "^1.1.2", + "@types/node": "^11.15.3", + "@types/statuses": "^1.5.0", + "@types/type-is": "^1.6.3", + "basic-auth": "^2.0.1", + "bluebird": "^3.7.2", + "eslint": "7.0.0", + "lodash": "^4.17.20", + "promisify-any": "^2.0.1", + "statuses": "^2.0.0", + "tslib": "^1.10.0", + "type-is": "^1.6.18" } } diff --git a/scripts/build-prod.js b/scripts/build-prod.js new file mode 100644 index 000000000..3f2771837 --- /dev/null +++ b/scripts/build-prod.js @@ -0,0 +1,10 @@ +const fs = require('fs'); +const packageJson = require('../package.json'); + +delete packageJson.scripts; +delete packageJson.devDependencies; +packageJson.main = 'index.js'; +fs.writeFileSync( + __dirname + '/../dist/package.json', + JSON.stringify(packageJson, null, 2), +); diff --git a/test/.mocharc.jsonc b/test/.mocharc.jsonc new file mode 100644 index 000000000..a8ff95b9d --- /dev/null +++ b/test/.mocharc.jsonc @@ -0,0 +1,7 @@ +{ + "require": ["ts-node/register", "should", "test/assertions"], + "reporter": "spec", + "slow": 75, + "timeout": 2000, + "ui": "bdd" +} diff --git a/test/assertions.js b/test/assertions.js old mode 100644 new mode 100755 index 6846e3cdf..f057cdeb5 --- a/test/assertions.js +++ b/test/assertions.js @@ -1,17 +1,12 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var should = require('should'); - /** * SHA-1 assertion. */ -should.Assertion.add('sha1', function() { - this.params = { operator: 'to be a valid SHA-1 hash' }; - - this.obj.should.match(/^[a-f0-9]{40}$/i); -}, true); +should.Assertion.add( + 'sha1', + function() { + this.params = { operator: 'to be a valid SHA-1 hash' }; + this.obj.should.match(/^[a-f0-9]{40}$/i); + }, + true, +); diff --git a/test/integration/grant-types/abstract-grant-type.spec.ts b/test/integration/grant-types/abstract-grant-type.spec.ts new file mode 100755 index 000000000..2bde67443 --- /dev/null +++ b/test/integration/grant-types/abstract-grant-type.spec.ts @@ -0,0 +1,231 @@ +import * as should from 'should'; +import { InvalidArgumentError } from '../../../lib/errors'; +import { AbstractGrantType } from '../../../lib/grant-types'; +import { Request } from '../../../lib/request'; +/** + * Test `AbstractGrantType` integration. + */ +describe('AbstractGrantType integration', () => { + describe('constructor()', () => { + it('should throw an error if `options.accessTokenLifetime` is missing', () => { + try { + new AbstractGrantType(); + should.fail('no error was thrown', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `accessTokenLifetime`'); + } + }); + + it('should throw an error if `options.model` is missing', () => { + try { + new AbstractGrantType({ accessTokenLifetime: 123 }); + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `model`'); + } + }); + + it('should set the `accessTokenLifetime`', () => { + const grantType = new AbstractGrantType({ + accessTokenLifetime: 123, + model: {}, + }); + + grantType.accessTokenLifetime.should.equal(123); + }); + + it('should set the `model`', () => { + const model = {}; + const grantType = new AbstractGrantType({ + accessTokenLifetime: 123, + model, + }); + + grantType.model.should.equal(model); + }); + + it('should set the `refreshTokenLifetime`', () => { + const grantType = new AbstractGrantType({ + accessTokenLifetime: 123, + model: {}, + refreshTokenLifetime: 456, + }); + + grantType.refreshTokenLifetime.should.equal(456); + }); + }); + + describe('generateAccessToken()', () => { + it('should return an access token', async () => { + const handler = new AbstractGrantType({ + accessTokenLifetime: 123, + model: {}, + refreshTokenLifetime: 456, + }); + try { + const data: any = await handler.generateAccessToken(); + data.should.be.a.sha1(); + } catch (error) { + should.fail('should.fail', ''); + } + }); + + it('should support promises', () => { + const model = { + generateAccessToken() { + return Promise.resolve({}); + }, + }; + const handler = new AbstractGrantType({ + accessTokenLifetime: 123, + model, + refreshTokenLifetime: 456, + }); + + handler.generateAccessToken().should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const model = { + generateAccessToken() { + return {}; + }, + }; + const handler = new AbstractGrantType({ + accessTokenLifetime: 123, + model, + refreshTokenLifetime: 456, + }); + + handler.generateAccessToken().should.be.an.instanceOf(Promise); + }); + }); + + describe('generateRefreshToken()', () => { + it('should return a refresh token', async () => { + const handler = new AbstractGrantType({ + accessTokenLifetime: 123, + model: {}, + refreshTokenLifetime: 456, + }); + try { + const data: any = await handler.generateRefreshToken(); + data.should.be.a.sha1(); + } catch (error) { + should.fail('should.fail fail', error.message); + } + }); + + it('should support promises', () => { + const model = { + generateRefreshToken() { + return Promise.resolve({}); + }, + }; + const handler = new AbstractGrantType({ + accessTokenLifetime: 123, + model, + refreshTokenLifetime: 456, + }); + + handler.generateRefreshToken().should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const model = { + generateRefreshToken() { + return {}; + }, + }; + const handler = new AbstractGrantType({ + accessTokenLifetime: 123, + model, + refreshTokenLifetime: 456, + }); + + handler.generateRefreshToken().should.be.an.instanceOf(Promise); + }); + }); + + describe('getAccessTokenExpiresAt()', () => { + it('should return a date', () => { + const handler = new AbstractGrantType({ + accessTokenLifetime: 123, + model: {}, + refreshTokenLifetime: 456, + }); + + handler.getAccessTokenExpiresAt().should.be.an.instanceOf(Date); + }); + }); + + describe('getRefreshTokenExpiresAt()', () => { + it('should return a refresh token', () => { + const handler = new AbstractGrantType({ + accessTokenLifetime: 123, + model: {}, + refreshTokenLifetime: 456, + }); + + handler.getRefreshTokenExpiresAt().should.be.an.instanceOf(Date); + }); + }); + + describe('getScope()', () => { + it('should throw an error if `scope` is invalid', () => { + const handler = new AbstractGrantType({ + accessTokenLifetime: 123, + model: {}, + refreshTokenLifetime: 456, + }); + const request = new Request({ + body: { scope: 'øå€£‰' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + handler.getScope(request); + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Invalid parameter: `scope`'); + } + }); + + it('should allow the `scope` to be `undefined`', () => { + const handler = new AbstractGrantType({ + accessTokenLifetime: 123, + model: {}, + refreshTokenLifetime: 456, + }); + const request = new Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + + should.not.exist(handler.getScope(request)); + }); + + it('should return the scope', () => { + const handler = new AbstractGrantType({ + accessTokenLifetime: 123, + model: {}, + refreshTokenLifetime: 456, + }); + const request = new Request({ + body: { scope: 'foo' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + handler.getScope(request).should.equal('foo'); + }); + }); +}); diff --git a/test/integration/grant-types/abstract-grant-type_test.js b/test/integration/grant-types/abstract-grant-type_test.js deleted file mode 100644 index 6da489cd9..000000000 --- a/test/integration/grant-types/abstract-grant-type_test.js +++ /dev/null @@ -1,174 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var AbstractGrantType = require('../../../lib/grant-types/abstract-grant-type'); -var InvalidArgumentError = require('../../../lib/errors/invalid-argument-error'); -var Promise = require('bluebird'); -var Request = require('../../../lib/request'); -var should = require('should'); - -/** - * Test `AbstractGrantType` integration. - */ - -describe('AbstractGrantType integration', function() { - describe('constructor()', function() { - it('should throw an error if `options.accessTokenLifetime` is missing', function() { - try { - new AbstractGrantType(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `accessTokenLifetime`'); - } - }); - - it('should throw an error if `options.model` is missing', function() { - try { - new AbstractGrantType({ accessTokenLifetime: 123 }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `model`'); - } - }); - - it('should set the `accessTokenLifetime`', function() { - var grantType = new AbstractGrantType({ accessTokenLifetime: 123, model: {} }); - - grantType.accessTokenLifetime.should.equal(123); - }); - - it('should set the `model`', function() { - var model = {}; - var grantType = new AbstractGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.model.should.equal(model); - }); - - it('should set the `refreshTokenLifetime`', function() { - var grantType = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); - - grantType.refreshTokenLifetime.should.equal(456); - }); - }); - - describe('generateAccessToken()', function() { - it('should return an access token', function() { - var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); - - return handler.generateAccessToken() - .then(function(data) { - data.should.be.a.sha1; - }) - .catch(should.fail); - }); - - it('should support promises', function() { - var model = { - generateAccessToken: function() { - return Promise.resolve({}); - } - }; - var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: model, refreshTokenLifetime: 456 }); - - handler.generateAccessToken().should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var model = { - generateAccessToken: function() { - return {}; - } - }; - var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: model, refreshTokenLifetime: 456 }); - - handler.generateAccessToken().should.be.an.instanceOf(Promise); - }); - }); - - describe('generateRefreshToken()', function() { - it('should return a refresh token', function() { - var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); - - return handler.generateRefreshToken() - .then(function(data) { - data.should.be.a.sha1; - }) - .catch(should.fail); - }); - - it('should support promises', function() { - var model = { - generateRefreshToken: function() { - return Promise.resolve({}); - } - }; - var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: model, refreshTokenLifetime: 456 }); - - handler.generateRefreshToken().should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var model = { - generateRefreshToken: function() { - return {}; - } - }; - var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: model, refreshTokenLifetime: 456 }); - - handler.generateRefreshToken().should.be.an.instanceOf(Promise); - }); - }); - - describe('getAccessTokenExpiresAt()', function() { - it('should return a date', function() { - var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); - - handler.getAccessTokenExpiresAt().should.be.an.instanceOf(Date); - }); - }); - - describe('getRefreshTokenExpiresAt()', function() { - it('should return a refresh token', function() { - var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); - - handler.getRefreshTokenExpiresAt().should.be.an.instanceOf(Date); - }); - }); - - describe('getScope()', function() { - it('should throw an error if `scope` is invalid', function() { - var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); - var request = new Request({ body: { scope: 'øå€£‰' }, headers: {}, method: {}, query: {} }); - - try { - handler.getScope(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid parameter: `scope`'); - } - }); - - it('should allow the `scope` to be `undefined`', function() { - var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - should.not.exist(handler.getScope(request)); - }); - - it('should return the scope', function() { - var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); - var request = new Request({ body: { scope: 'foo' }, headers: {}, method: {}, query: {} }); - - handler.getScope(request).should.equal('foo'); - }); - }); -}); diff --git a/test/integration/grant-types/authorization-code-grant-type.spec.ts b/test/integration/grant-types/authorization-code-grant-type.spec.ts new file mode 100755 index 000000000..f052a7b34 --- /dev/null +++ b/test/integration/grant-types/authorization-code-grant-type.spec.ts @@ -0,0 +1,1033 @@ +import * as should from 'should'; +import { + InvalidArgumentError, + InvalidGrantError, + InvalidRequestError, + ServerError, +} from '../../../lib/errors'; +import { AuthorizationCodeGrantType } from '../../../lib/grant-types'; +import { Request } from '../../../lib/request'; + +/** + * Test `AuthorizationCodeGrantType` integration. + */ + +describe('AuthorizationCodeGrantType integration', () => { + describe('constructor()', () => { + it('should throw an error if `model` is missing', () => { + try { + new AuthorizationCodeGrantType({ accessTokenLifetime: 3600 }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `model`'); + } + }); + + it('should throw an error if the model does not implement `getAuthorizationCode()`', () => { + try { + new AuthorizationCodeGrantType({ + accessTokenLifetime: 3600, + model: {}, + }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `getAuthorizationCode()`', + ); + } + }); + + it('should throw an error if the model does not implement `revokeAuthorizationCode()`', () => { + try { + const model = { + getAuthorizationCode: () => {}, + }; + + new AuthorizationCodeGrantType({ accessTokenLifetime: 3600, model }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `revokeAuthorizationCode()`', + ); + } + }); + + it('should throw an error if the model does not implement `saveToken()`', () => { + try { + const model = { + getAuthorizationCode: () => {}, + revokeAuthorizationCode: () => {}, + }; + + new AuthorizationCodeGrantType({ accessTokenLifetime: 3600, model }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `saveToken()`', + ); + } + }); + }); + + describe('handle()', () => { + it('should throw an error if `request` is missing', async () => { + const model = { + getAuthorizationCode: () => {}, + revokeAuthorizationCode: () => {}, + saveToken: () => {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + + try { + await grantType.handle(undefined, undefined); + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `request`'); + } + }); + + it('should throw an error if `client` is invalid', () => { + const client: any = {}; + const model = { + getAuthorizationCode() { + return { + authorizationCode: 12345, + expiresAt: new Date(new Date().getTime() * 2), + user: {}, + }; + }, + revokeAuthorizationCode() {}, + saveToken() {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return grantType + .handle(request, client) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(ServerError); + e.message.should.equal( + 'Server error: `getAuthorizationCode()` did not return a `client` object', + ); + }); + }); + + it('should throw an error if `client` is missing', async () => { + const model = { + getAuthorizationCode: () => { + return { + authorizationCode: 12345, + expiresAt: new Date(new Date().getTime() * 2), + user: {}, + }; + }, + revokeAuthorizationCode: () => {}, + saveToken: () => {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + await grantType.handle(request, undefined); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `client`'); + } + }); + + it('should return a token', async () => { + const client: any = { id: 'foobar' }; + const token = {}; + const model = { + getAuthorizationCode: () => { + return { + authorizationCode: 12345, + client: { id: 'foobar' }, + expiresAt: new Date(new Date().getTime() * 2), + user: {}, + }; + }, + revokeAuthorizationCode: () => { + return true; + }, + saveToken: () => { + return token; + }, + validateScope: () => { + return 'foo'; + }, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + const data = await grantType.handle(request, client); + data.should.equal(token); + } catch (e) { + should.fail('should.fail', ''); + } + }); + + it('should support promises', () => { + const client: any = { id: 'foobar' }; + const model = { + getAuthorizationCode: () => { + return Promise.resolve({ + authorizationCode: 12345, + client: { id: 'foobar' }, + expiresAt: new Date(new Date().getTime() * 2), + user: {}, + }); + }, + revokeAuthorizationCode: () => { + return true; + }, + saveToken: () => {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + + grantType.handle(request, client).should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const client: any = { id: 'foobar' }; + const model = { + getAuthorizationCode: () => { + return { + authorizationCode: 12345, + client: { id: 'foobar' }, + expiresAt: new Date(new Date().getTime() * 2), + user: {}, + }; + }, + revokeAuthorizationCode: () => { + return true; + }, + saveToken: () => {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + + grantType.handle(request, client).should.be.an.instanceOf(Promise); + }); + + // it('should support callbacks', () => { + // const client: any = { id: 'foobar' }; + // const model = { + // getAuthorizationCode: (code, callback) => { + // callback(undefined, { + // authorizationCode: 12345, + // client: { id: 'foobar' }, + // expiresAt: new Date(new Date().getTime() * 2), + // user: {}, + // }); + // }, + // revokeAuthorizationCode: (code, callback) => { + // callback(undefined, { + // authorizationCode: 12345, + // client: { id: 'foobar' }, + // expiresAt: new Date(new Date().getTime() / 2), + // user: {}, + // }); + // }, + // saveToken: (tokenToSave, client, user, callback) => { + // callback(undefined, tokenToSave); + // }, + // }; + // const grantType = new AuthorizationCodeGrantType({ + // accessTokenLifetime: 123, + // model, + // }); + // const request = new Request({ + // body: { code: 12345 }, + // headers: {}, + // method: "ANY", + // query: {}, + // }); + + // grantType.handle(request, client).should.be.an.instanceOf(Promise); + // }); + }); + + describe('getAuthorizationCode()', () => { + it('should throw an error if the request body does not contain `code`', async () => { + const client: any = {}; + const model = { + getAuthorizationCode: () => {}, + revokeAuthorizationCode: () => {}, + saveToken: () => {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + await grantType.getAuthorizationCode(request, client); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Missing parameter: `code`'); + } + }); + + it('should throw an error if `code` is invalid', async () => { + const client: any = {}; + const model = { + getAuthorizationCode: () => {}, + revokeAuthorizationCode: () => {}, + saveToken: () => {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 'øå€£‰' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + await grantType.getAuthorizationCode(request, client); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Invalid parameter: `code`'); + } + }); + + it('should throw an error if `authorizationCode` is missing', () => { + const client: any = {}; + const model = { + getAuthorizationCode: () => {}, + revokeAuthorizationCode: () => {}, + saveToken: () => {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return grantType + .getAuthorizationCode(request, client) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(InvalidGrantError); + e.message.should.equal( + 'Invalid grant: authorization code is invalid', + ); + }); + }); + + it('should throw an error if `authorizationCode.client` is missing', () => { + const client: any = {}; + const model = { + getAuthorizationCode: () => { + return { authorizationCode: 12345 }; + }, + revokeAuthorizationCode: () => {}, + saveToken: () => {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return grantType + .getAuthorizationCode(request, client) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(ServerError); + e.message.should.equal( + 'Server error: `getAuthorizationCode()` did not return a `client` object', + ); + }); + }); + + it('should throw an error if `authorizationCode.expiresAt` is missing', () => { + const client: any = {}; + const model = { + getAuthorizationCode: () => { + return { authorizationCode: 12345, client: {}, user: {} }; + }, + revokeAuthorizationCode: () => {}, + saveToken: () => {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return grantType + .getAuthorizationCode(request, client) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(ServerError); + e.message.should.equal( + 'Server error: `expiresAt` must be a Date instance', + ); + }); + }); + + it('should throw an error if `authorizationCode.user` is missing', () => { + const client: any = {}; + const model = { + getAuthorizationCode: () => { + return { + authorizationCode: 12345, + client: {}, + expiresAt: new Date(), + }; + }, + revokeAuthorizationCode: () => {}, + saveToken: () => {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return grantType + .getAuthorizationCode(request, client) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(ServerError); + e.message.should.equal( + 'Server error: `getAuthorizationCode()` did not return a `user` object', + ); + }); + }); + + it('should throw an error if the client id does not match', () => { + const client: any = { id: 123 }; + const model = { + getAuthorizationCode() { + return { + authorizationCode: 12345, + expiresAt: new Date(), + client: { id: 456 }, + user: {}, + }; + }, + revokeAuthorizationCode() {}, + saveToken() {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return grantType + .getAuthorizationCode(request, client) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(InvalidGrantError); + e.message.should.equal( + 'Invalid grant: authorization code is invalid', + ); + }); + }); + + it('should throw an error if the auth code is expired', () => { + const client: any = { id: 123 }; + const date = new Date(new Date().getTime() / 2); + const model = { + getAuthorizationCode() { + return { + authorizationCode: 12345, + client: { id: 123 }, + expiresAt: date, + user: {}, + }; + }, + revokeAuthorizationCode() {}, + saveToken() {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return grantType + .getAuthorizationCode(request, client) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(InvalidGrantError); + e.message.should.equal( + 'Invalid grant: authorization code has expired', + ); + }); + }); + + it('should throw an error if the `redirectUri` is invalid', () => { + const authorizationCode = { + authorizationCode: 12345, + client: { id: 'foobar' }, + expiresAt: new Date(new Date().getTime() * 2), + redirectUri: 'foobar', + user: {}, + }; + const client: any = { id: 'foobar' }; + const model = { + getAuthorizationCode() { + return authorizationCode; + }, + revokeAuthorizationCode() {}, + saveToken() {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return grantType + .getAuthorizationCode(request, client) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(InvalidGrantError); + e.message.should.equal( + 'Invalid grant: `redirect_uri` is not a valid URI', + ); + }); + }); + + it('should return an auth code', () => { + const authorizationCode = { + authorizationCode: 12345, + client: { id: 'foobar' }, + expiresAt: new Date(new Date().getTime() * 2), + user: {}, + }; + const client: any = { id: 'foobar' }; + const model = { + getAuthorizationCode() { + return authorizationCode; + }, + revokeAuthorizationCode() {}, + saveToken() {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return grantType + .getAuthorizationCode(request, client) + .then(data => { + data.should.equal(authorizationCode); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + + it('should support promises', () => { + const authorizationCode = { + authorizationCode: 12345, + client: { id: 'foobar' }, + expiresAt: new Date(new Date().getTime() * 2), + user: {}, + }; + const client: any = { id: 'foobar' }; + const model = { + getAuthorizationCode() { + return Promise.resolve(authorizationCode); + }, + revokeAuthorizationCode() {}, + saveToken() {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + + grantType + .getAuthorizationCode(request, client) + .should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const authorizationCode = { + authorizationCode: 12345, + client: { id: 'foobar' }, + expiresAt: new Date(new Date().getTime() * 2), + user: {}, + }; + const client: any = { id: 'foobar' }; + const model = { + getAuthorizationCode() { + return authorizationCode; + }, + revokeAuthorizationCode() {}, + saveToken() {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + + grantType + .getAuthorizationCode(request, client) + .should.be.an.instanceOf(Promise); + }); + + // it('should support callbacks', () => { + // const authorizationCode = { + // authorizationCode: 12345, + // client: { id: 'foobar' }, + // expiresAt: new Date(new Date().getTime() * 2), + // user: {}, + // }; + // const client: any = { id: 'foobar' }; + // const model = { + // getAuthorizationCode(code, callback) { + // callback(undefined, authorizationCode); + // }, + // revokeAuthorizationCode() {}, + // saveToken() {}, + // }; + // const grantType = new AuthorizationCodeGrantType({ + // accessTokenLifetime: 123, + // model, + // }); + // const request = new Request({ + // body: { code: 12345 }, + // headers: {}, + // method: "ANY", + // query: {}, + // }); + + // grantType + // .getAuthorizationCode(request, client) + // .should.be.an.instanceOf(Promise); + // }); + }); + + describe('validateRedirectUri()', () => { + it('should throw an error if `redirectUri` is missing', () => { + const authorizationCode: any = { + authorizationCode: 12345, + client: {}, + expiresAt: new Date(new Date().getTime() / 2), + redirectUri: 'http://foo.bar', + user: {}, + }; + const model = { + getAuthorizationCode() {}, + revokeAuthorizationCode() { + return authorizationCode; + }, + saveToken() {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + grantType.validateRedirectUri(request, authorizationCode); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal( + 'Invalid request: `redirect_uri` is not a valid URI', + ); + } + }); + + it('should throw an error if `redirectUri` is invalid', () => { + const authorizationCode: any = { + authorizationCode: 12345, + client: {}, + expiresAt: new Date(new Date().getTime() / 2), + redirectUri: 'http://foo.bar', + user: {}, + }; + const model = { + getAuthorizationCode() {}, + revokeAuthorizationCode() { + return true; + }, + saveToken() {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { code: 12345, redirect_uri: 'http://bar.foo' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + grantType.validateRedirectUri(request, authorizationCode); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Invalid request: `redirect_uri` is invalid'); + } + }); + }); + + describe('revokeAuthorizationCode()', () => { + it('should revoke the auth code', async () => { + const authorizationCode: any = { + authorizationCode: 12345, + client: {}, + expiresAt: new Date(new Date().getTime() / 2), + user: {}, + }; + const model = { + getAuthorizationCode() {}, + revokeAuthorizationCode() { + return true; + }, + saveToken() {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + try { + const data = await grantType.revokeAuthorizationCode(authorizationCode); + data.should.equal(authorizationCode); + } catch (error) { + should.fail('should.fail', ''); + } + }); + + it('should throw an error when the auth code is invalid', () => { + const authorizationCode: any = { + authorizationCode: 12345, + client: {}, + expiresAt: new Date(new Date().getTime() / 2), + user: {}, + }; + const model = { + getAuthorizationCode() {}, + revokeAuthorizationCode() { + return false; + }, + saveToken() {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + + return grantType + .revokeAuthorizationCode(authorizationCode) + .then(data => { + data.should.equal(authorizationCode); + }) + .catch(e => { + e.should.be.an.instanceOf(InvalidGrantError); + e.message.should.equal( + 'Invalid grant: authorization code is invalid', + ); + }); + }); + + it('should support promises', () => { + const authorizationCode: any = { + authorizationCode: 12345, + client: {}, + expiresAt: new Date(new Date().getTime() / 2), + user: {}, + }; + const model = { + getAuthorizationCode() {}, + revokeAuthorizationCode() { + return Promise.resolve(true); + }, + saveToken() {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + + grantType + .revokeAuthorizationCode(authorizationCode) + .should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const authorizationCode: any = { + authorizationCode: 12345, + client: {}, + expiresAt: new Date(new Date().getTime() / 2), + user: {}, + }; + const model = { + getAuthorizationCode() {}, + revokeAuthorizationCode() { + return authorizationCode; + }, + saveToken() {}, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + + grantType + .revokeAuthorizationCode(authorizationCode) + .should.be.an.instanceOf(Promise); + }); + + // it('should support callbacks', () => { + // const authorizationCode = { + // authorizationCode: 12345, + // client: {}, + // expiresAt: new Date(new Date().getTime() / 2), + // user: {}, + // }; + // const model = { + // getAuthorizationCode() {}, + // revokeAuthorizationCode(code, callback) { + // callback(undefined, authorizationCode); + // }, + // saveToken() {}, + // }; + // const grantType = new AuthorizationCodeGrantType({ + // accessTokenLifetime: 123, + // model, + // }); + + // grantType + // .revokeAuthorizationCode(authorizationCode) + // .should.be.an.instanceOf(Promise); + // }); + }); + + describe('saveToken()', () => { + it('should save the token', async () => { + const token: any = {}; + const model = { + getAuthorizationCode() {}, + revokeAuthorizationCode() {}, + saveToken() { + return token; + }, + validateScope() { + return 'foo'; + }, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + try { + const data = await grantType.saveToken({}, {} as any, token, ''); + data.should.equal(token); + } catch (error) { + should.fail('should.fail', ''); + } + }); + + it('should support promises', () => { + const token: any = {}; + const model = { + getAuthorizationCode() {}, + revokeAuthorizationCode() {}, + saveToken() { + return Promise.resolve(token); + }, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + + grantType + .saveToken({}, {} as any, token, '') + .should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const token: any = {}; + const model = { + getAuthorizationCode() {}, + revokeAuthorizationCode() {}, + saveToken() { + return token; + }, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + + grantType + .saveToken({}, {} as any, token, '') + .should.be.an.instanceOf(Promise); + }); + + /* it('should support callbacks', () => { + const token = {}; + const model = { + getAuthorizationCode() {}, + revokeAuthorizationCode() {}, + saveToken(tokenToSave, client, user, callback) { + callback(undefined, token); + }, + }; + const grantType = new AuthorizationCodeGrantType({ + accessTokenLifetime: 123, + model, + }); + + grantType.saveToken({}, {}, token, '').should.be.an.instanceOf(Promise); + }); */ + }); +}); diff --git a/test/integration/grant-types/authorization-code-grant-type_test.js b/test/integration/grant-types/authorization-code-grant-type_test.js deleted file mode 100644 index 7f84e3443..000000000 --- a/test/integration/grant-types/authorization-code-grant-type_test.js +++ /dev/null @@ -1,594 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var AuthorizationCodeGrantType = require('../../../lib/grant-types/authorization-code-grant-type'); -var InvalidArgumentError = require('../../../lib/errors/invalid-argument-error'); -var InvalidGrantError = require('../../../lib/errors/invalid-grant-error'); -var InvalidRequestError = require('../../../lib/errors/invalid-request-error'); -var Promise = require('bluebird'); -var Request = require('../../../lib/request'); -var ServerError = require('../../../lib/errors/server-error'); -var should = require('should'); - -/** - * Test `AuthorizationCodeGrantType` integration. - */ - -describe('AuthorizationCodeGrantType integration', function() { - describe('constructor()', function() { - it('should throw an error if `model` is missing', function() { - try { - new AuthorizationCodeGrantType(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `model`'); - } - }); - - it('should throw an error if the model does not implement `getAuthorizationCode()`', function() { - try { - new AuthorizationCodeGrantType({ model: {} }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: model does not implement `getAuthorizationCode()`'); - } - }); - - it('should throw an error if the model does not implement `revokeAuthorizationCode()`', function() { - try { - var model = { - getAuthorizationCode: function() {} - }; - - new AuthorizationCodeGrantType({ model: model }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: model does not implement `revokeAuthorizationCode()`'); - } - }); - - it('should throw an error if the model does not implement `saveToken()`', function() { - try { - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function() {} - }; - - new AuthorizationCodeGrantType({ model: model }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: model does not implement `saveToken()`'); - } - }); - }); - - describe('handle()', function() { - it('should throw an error if `request` is missing', function() { - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - - try { - grantType.handle(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `request`'); - } - }); - - it('should throw an error if `client` is invalid', function() { - var client = {}; - var model = { - getAuthorizationCode: function() { return { authorizationCode: 12345, expiresAt: new Date(new Date() * 2), user: {} }; }, - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - return grantType.handle(request, client) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(ServerError); - e.message.should.equal('Server error: `getAuthorizationCode()` did not return a `client` object'); - }); - }); - - it('should throw an error if `client` is missing', function() { - - var model = { - getAuthorizationCode: function() { return { authorizationCode: 12345, expiresAt: new Date(new Date() * 2), user: {} }; }, - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - try { - grantType.handle(request, null); - } - catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `client`'); - } - }); - - it('should return a token', function() { - var client = { id: 'foobar' }; - var token = {}; - var model = { - getAuthorizationCode: function() { return { authorizationCode: 12345, client: { id: 'foobar' }, expiresAt: new Date(new Date() * 2), user: {} }; }, - revokeAuthorizationCode: function() { return true; }, - saveToken: function() { return token; }, - validateScope: function() { return 'foo'; } - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - return grantType.handle(request, client) - .then(function(data) { - data.should.equal(token); - }) - .catch(should.fail); - }); - - it('should support promises', function() { - var client = { id: 'foobar' }; - var model = { - getAuthorizationCode: function() { return Promise.resolve({ authorizationCode: 12345, client: { id: 'foobar' }, expiresAt: new Date(new Date() * 2), user: {} }); }, - revokeAuthorizationCode: function() { return true; }, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - grantType.handle(request, client).should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var client = { id: 'foobar' }; - var model = { - getAuthorizationCode: function() { return { authorizationCode: 12345, client: { id: 'foobar' }, expiresAt: new Date(new Date() * 2), user: {} }; }, - revokeAuthorizationCode: function() { return true; }, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - grantType.handle(request, client).should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function() { - var client = { id: 'foobar' }; - var model = { - getAuthorizationCode: function(code, callback) { callback(null, { authorizationCode: 12345, client: { id: 'foobar' }, expiresAt: new Date(new Date() * 2), user: {} }); }, - revokeAuthorizationCode: function(code, callback) { callback(null, { authorizationCode: 12345, client: { id: 'foobar' }, expiresAt: new Date(new Date() / 2), user: {} }); }, - saveToken: function(tokenToSave, client, user, callback) { callback(null, tokenToSave); } - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - grantType.handle(request, client).should.be.an.instanceOf(Promise); - }); - }); - - describe('getAuthorizationCode()', function() { - it('should throw an error if the request body does not contain `code`', function() { - var client = {}; - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - try { - grantType.getAuthorizationCode(request, client); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Missing parameter: `code`'); - } - }); - - it('should throw an error if `code` is invalid', function() { - var client = {}; - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 'øå€£‰' }, headers: {}, method: {}, query: {} }); - - try { - grantType.getAuthorizationCode(request, client); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Invalid parameter: `code`'); - } - }); - - it('should throw an error if `authorizationCode` is missing', function() { - var client = {}; - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - return grantType.getAuthorizationCode(request, client) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidGrantError); - e.message.should.equal('Invalid grant: authorization code is invalid'); - }); - }); - - it('should throw an error if `authorizationCode.client` is missing', function() { - var client = {}; - var model = { - getAuthorizationCode: function() { return { authorizationCode: 12345 }; }, - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - return grantType.getAuthorizationCode(request, client) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(ServerError); - e.message.should.equal('Server error: `getAuthorizationCode()` did not return a `client` object'); - }); - }); - - it('should throw an error if `authorizationCode.expiresAt` is missing', function() { - var client = {}; - var model = { - getAuthorizationCode: function() { return { authorizationCode: 12345, client: {}, user: {} }; }, - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - return grantType.getAuthorizationCode(request, client) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(ServerError); - e.message.should.equal('Server error: `expiresAt` must be a Date instance'); - }); - }); - - it('should throw an error if `authorizationCode.user` is missing', function() { - var client = {}; - var model = { - getAuthorizationCode: function() { return { authorizationCode: 12345, client: {}, expiresAt: new Date() }; }, - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - return grantType.getAuthorizationCode(request, client) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(ServerError); - e.message.should.equal('Server error: `getAuthorizationCode()` did not return a `user` object'); - }); - }); - - it('should throw an error if the client id does not match', function() { - var client = { id: 123 }; - var model = { - getAuthorizationCode: function() { - return { authorizationCode: 12345, expiresAt: new Date(), client: { id: 456 }, user: {} }; - }, - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - return grantType.getAuthorizationCode(request, client) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidGrantError); - e.message.should.equal('Invalid grant: authorization code is invalid'); - }); - }); - - it('should throw an error if the auth code is expired', function() { - var client = { id: 123 }; - var date = new Date(new Date() / 2); - var model = { - getAuthorizationCode: function() { - return { authorizationCode: 12345, client: { id: 123 }, expiresAt: date, user: {} }; - }, - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - return grantType.getAuthorizationCode(request, client) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidGrantError); - e.message.should.equal('Invalid grant: authorization code has expired'); - }); - }); - - it('should throw an error if the `redirectUri` is invalid', function() { - var authorizationCode = { authorizationCode: 12345, client: { id: 'foobar' }, expiresAt: new Date(new Date() * 2), redirectUri: 'foobar', user: {} }; - var client = { id: 'foobar' }; - var model = { - getAuthorizationCode: function() { return authorizationCode; }, - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - return grantType.getAuthorizationCode(request, client) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidGrantError); - e.message.should.equal('Invalid grant: `redirect_uri` is not a valid URI'); - }); - }); - - it('should return an auth code', function() { - var authorizationCode = { authorizationCode: 12345, client: { id: 'foobar' }, expiresAt: new Date(new Date() * 2), user: {} }; - var client = { id: 'foobar' }; - var model = { - getAuthorizationCode: function() { return authorizationCode; }, - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - return grantType.getAuthorizationCode(request, client) - .then(function(data) { - data.should.equal(authorizationCode); - }) - .catch(should.fail); - }); - - it('should support promises', function() { - var authorizationCode = { authorizationCode: 12345, client: { id: 'foobar' }, expiresAt: new Date(new Date() * 2), user: {} }; - var client = { id: 'foobar' }; - var model = { - getAuthorizationCode: function() { return Promise.resolve(authorizationCode); }, - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - grantType.getAuthorizationCode(request, client).should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var authorizationCode = { authorizationCode: 12345, client: { id: 'foobar' }, expiresAt: new Date(new Date() * 2), user: {} }; - var client = { id: 'foobar' }; - var model = { - getAuthorizationCode: function() { return authorizationCode; }, - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - grantType.getAuthorizationCode(request, client).should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function() { - var authorizationCode = { authorizationCode: 12345, client: { id: 'foobar' }, expiresAt: new Date(new Date() * 2), user: {} }; - var client = { id: 'foobar' }; - var model = { - getAuthorizationCode: function(code, callback) { callback(null, authorizationCode); }, - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - grantType.getAuthorizationCode(request, client).should.be.an.instanceOf(Promise); - }); - }); - - describe('validateRedirectUri()', function() { - it('should throw an error if `redirectUri` is missing', function() { - var authorizationCode = { authorizationCode: 12345, client: {}, expiresAt: new Date(new Date() / 2), redirectUri: 'http://foo.bar', user: {} }; - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function() { return authorizationCode; }, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - - try { - grantType.validateRedirectUri(request, authorizationCode); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Invalid request: `redirect_uri` is not a valid URI'); - } - }); - - it('should throw an error if `redirectUri` is invalid', function() { - var authorizationCode = { authorizationCode: 12345, client: {}, expiresAt: new Date(new Date() / 2), redirectUri: 'http://foo.bar', user: {} }; - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function() { return true; }, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { code: 12345, redirect_uri: 'http://bar.foo' }, headers: {}, method: {}, query: {} }); - - try { - grantType.validateRedirectUri(request, authorizationCode); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Invalid request: `redirect_uri` is invalid'); - } - }); - }); - - describe('revokeAuthorizationCode()', function() { - it('should revoke the auth code', function() { - var authorizationCode = { authorizationCode: 12345, client: {}, expiresAt: new Date(new Date() / 2), user: {} }; - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function() { return true; }, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - - return grantType.revokeAuthorizationCode(authorizationCode) - .then(function(data) { - data.should.equal(authorizationCode); - }) - .catch(should.fail); - }); - - it('should throw an error when the auth code is invalid', function() { - var authorizationCode = { authorizationCode: 12345, client: {}, expiresAt: new Date(new Date() / 2), user: {} }; - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function() { return false; }, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - - return grantType.revokeAuthorizationCode(authorizationCode) - .then(function(data) { - data.should.equal(authorizationCode); - }) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidGrantError); - e.message.should.equal('Invalid grant: authorization code is invalid'); - }); - }); - - it('should support promises', function() { - var authorizationCode = { authorizationCode: 12345, client: {}, expiresAt: new Date(new Date() / 2), user: {} }; - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function() { return Promise.resolve(true); }, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.revokeAuthorizationCode(authorizationCode).should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var authorizationCode = { authorizationCode: 12345, client: {}, expiresAt: new Date(new Date() / 2), user: {} }; - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function() { return authorizationCode; }, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.revokeAuthorizationCode(authorizationCode).should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function() { - var authorizationCode = { authorizationCode: 12345, client: {}, expiresAt: new Date(new Date() / 2), user: {} }; - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function(code, callback) { callback(null, authorizationCode); }, - saveToken: function() {} - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.revokeAuthorizationCode(authorizationCode).should.be.an.instanceOf(Promise); - }); - }); - - describe('saveToken()', function() { - it('should save the token', function() { - var token = {}; - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function() {}, - saveToken: function() { return token; }, - validateScope: function() { return 'foo'; } - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - - return grantType.saveToken(token) - .then(function(data) { - data.should.equal(token); - }) - .catch(should.fail); - }); - - it('should support promises', function() { - var token = {}; - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function() {}, - saveToken: function() { return Promise.resolve(token); } - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.saveToken(token).should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var token = {}; - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function() {}, - saveToken: function() { return token; } - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.saveToken(token).should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function() { - var token = {}; - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function() {}, - saveToken: function(tokenToSave, client, user, callback) { callback(null, token); } - }; - var grantType = new AuthorizationCodeGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.saveToken(token).should.be.an.instanceOf(Promise); - }); - }); -}); diff --git a/test/integration/grant-types/client-credentials-grant-type.spec.ts b/test/integration/grant-types/client-credentials-grant-type.spec.ts new file mode 100755 index 000000000..d9f1e3a9a --- /dev/null +++ b/test/integration/grant-types/client-credentials-grant-type.spec.ts @@ -0,0 +1,382 @@ +import * as should from 'should'; +import { InvalidArgumentError, InvalidGrantError } from '../../../lib/errors'; +import { ClientCredentialsGrantType } from '../../../lib/grant-types'; +import { Request } from '../../../lib/request'; + +/** + * Test `ClientCredentialsGrantType` integration. + */ + +describe('ClientCredentialsGrantType integration', () => { + describe('constructor()', () => { + it('should throw an error if `model` is missing', () => { + try { + new ClientCredentialsGrantType({ accessTokenLifetime: 3600 }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `model`'); + } + }); + + it('should throw an error if the model does not implement `getUserFromClient()`', () => { + try { + new ClientCredentialsGrantType({ + accessTokenLifetime: 3600, + model: {}, + }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `getUserFromClient()`', + ); + } + }); + + it('should throw an error if the model does not implement `saveToken()`', () => { + try { + const model = { + getUserFromClient() {}, + }; + + new ClientCredentialsGrantType({ accessTokenLifetime: 3600, model }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `saveToken()`', + ); + } + }); + }); + + describe('handle()', () => { + it('should throw an error if `request` is missing', async () => { + const model = { + getUserFromClient() {}, + saveToken() {}, + }; + const grantType = new ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + + try { + await grantType.handle(undefined, undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `request`'); + } + }); + + it('should throw an error if `client` is missing', async () => { + const model = { + getUserFromClient() {}, + saveToken() {}, + }; + const grantType = new ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + await grantType.handle(request, undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `client`'); + } + }); + + it('should return a token', () => { + const token = {}; + const model = { + getUserFromClient() { + return {}; + }, + saveToken() { + return token; + }, + validateScope() { + return 'foo'; + }, + }; + const grantType = new ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + + return grantType + .handle(request, {} as any) + .then(data => { + data.should.equal(token); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + + it('should support promises', () => { + const token = {}; + const model = { + getUserFromClient() { + return {}; + }, + saveToken() { + return token; + }, + }; + const grantType = new ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + + grantType.handle(request, {} as any).should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const token = {}; + const model = { + getUserFromClient() { + return {}; + }, + saveToken() { + return token; + }, + }; + const grantType = new ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + + grantType.handle(request, {} as any).should.be.an.instanceOf(Promise); + }); + }); + + describe('getUserFromClient()', () => { + it('should throw an error if `user` is missing', () => { + const model = { + getUserFromClient() {}, + saveToken() {}, + }; + const grantType = new ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + + return grantType + .getUserFromClient({} as any) + .then(() => { + should.fail('should.fail', ''); + }) + .catch((e: any) => { + e.should.be.an.instanceOf(InvalidGrantError); + e.message.should.equal('Invalid grant: user credentials are invalid'); + }); + }); + + it('should return a user', async () => { + const user = { email: 'foo@bar.com' }; + const model = { + getUserFromClient() { + return user; + }, + saveToken() {}, + }; + const grantType = new ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + const data = await grantType.getUserFromClient({} as any); + data.should.equal(user); + } catch (error) { + should.fail('should.fail', ''); + } + }); + + it('should support promises', () => { + const user = { email: 'foo@bar.com' }; + const model = { + getUserFromClient() { + return Promise.resolve(user); + }, + saveToken() {}, + }; + const grantType = new ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + + grantType.getUserFromClient({} as any).should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const user = { email: 'foo@bar.com' }; + const model = { + getUserFromClient() { + return user; + }, + saveToken() {}, + }; + const grantType = new ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + + grantType.getUserFromClient({} as any).should.be.an.instanceOf(Promise); + }); + + /* it('should support callbacks', () => { + const user = { email: 'foo@bar.com' }; + const model = { + getUserFromClient(userId, callback) { + callback(null, user); + }, + saveToken() {}, + }; + const grantType = new ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + + grantType.getUserFromClient({}).should.be.an.instanceOf(Promise); + }); */ + }); + + describe('saveToken()', () => { + it('should save the token', async () => { + const token: any = {}; + const model = { + getUserFromClient() {}, + saveToken() { + return token; + }, + validateScope() { + return 'foo'; + }, + }; + const grantType = new ClientCredentialsGrantType({ + accessTokenLifetime: 123, + model, + }); + try { + const data = await grantType.saveToken({}, {} as any, token); + data.should.equal(token); + } catch (error) { + should.fail('should.fail', ''); + } + }); + + it('should support promises', () => { + const token: any = {}; + const model = { + getUserFromClient() {}, + saveToken() { + return Promise.resolve(token); + }, + }; + const grantType = new ClientCredentialsGrantType({ + accessTokenLifetime: 123, + model, + }); + + grantType + .saveToken({}, {} as any, token) + .should.be.an.instanceOf(Promise); + }); + + /* it('should support non-promises', () => { + const token = {}; + const model = { + getUserFromClient() {}, + saveToken() { + return token; + }, + }; + const grantType = new ClientCredentialsGrantType({ + accessTokenLifetime: 123, + model, + }); + + grantType.saveToken(token).should.be.an.instanceOf(Promise); + }); */ + + /* it('should support callbacks', () => { + const token = {}; + const model = { + getUserFromClient() {}, + saveToken(tokenToSave, client, user, callback) { + callback(null, token); + }, + }; + const grantType = new ClientCredentialsGrantType({ + accessTokenLifetime: 123, + model, + }); + + grantType.saveToken(token).should.be.an.instanceOf(Promise); + }); */ + }); +}); diff --git a/test/integration/grant-types/client-credentials-grant-type_test.js b/test/integration/grant-types/client-credentials-grant-type_test.js deleted file mode 100644 index 15ec9cc0e..000000000 --- a/test/integration/grant-types/client-credentials-grant-type_test.js +++ /dev/null @@ -1,256 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var ClientCredentialsGrantType = require('../../../lib/grant-types/client-credentials-grant-type'); -var InvalidArgumentError = require('../../../lib/errors/invalid-argument-error'); -var InvalidGrantError = require('../../../lib/errors/invalid-grant-error'); -var Promise = require('bluebird'); -var Request = require('../../../lib/request'); -var should = require('should'); - -/** - * Test `ClientCredentialsGrantType` integration. - */ - -describe('ClientCredentialsGrantType integration', function() { - describe('constructor()', function() { - it('should throw an error if `model` is missing', function() { - try { - new ClientCredentialsGrantType(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `model`'); - } - }); - - it('should throw an error if the model does not implement `getUserFromClient()`', function() { - try { - new ClientCredentialsGrantType({ model: {} }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: model does not implement `getUserFromClient()`'); - } - }); - - it('should throw an error if the model does not implement `saveToken()`', function() { - try { - var model = { - getUserFromClient: function() {} - }; - - new ClientCredentialsGrantType({ model: model }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: model does not implement `saveToken()`'); - } - }); - }); - - describe('handle()', function() { - it('should throw an error if `request` is missing', function() { - var model = { - getUserFromClient: function() {}, - saveToken: function() {} - }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); - - try { - grantType.handle(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `request`'); - } - }); - - it('should throw an error if `client` is missing', function() { - var model = { - getUserFromClient: function() {}, - saveToken: function() {} - }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - try { - grantType.handle(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `client`'); - } - }); - - it('should return a token', function() { - var token = {}; - var model = { - getUserFromClient: function() { return {}; }, - saveToken: function() { return token; }, - validateScope: function() { return 'foo'; } - }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - return grantType.handle(request, {}) - .then(function(data) { - data.should.equal(token); - }) - .catch(should.fail); - }); - - it('should support promises', function() { - var token = {}; - var model = { - getUserFromClient: function() { return {}; }, - saveToken: function() { return token; } - }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - grantType.handle(request, {}).should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var token = {}; - var model = { - getUserFromClient: function() { return {}; }, - saveToken: function() { return token; } - }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - grantType.handle(request, {}).should.be.an.instanceOf(Promise); - }); - }); - - describe('getUserFromClient()', function() { - it('should throw an error if `user` is missing', function() { - var model = { - getUserFromClient: function() {}, - saveToken: function() {} - }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - return grantType.getUserFromClient(request, {}) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidGrantError); - e.message.should.equal('Invalid grant: user credentials are invalid'); - }); - }); - - it('should return a user', function() { - var user = { email: 'foo@bar.com' }; - var model = { - getUserFromClient: function() { return user; }, - saveToken: function() {} - }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - return grantType.getUserFromClient(request, {}) - .then(function(data) { - data.should.equal(user); - }) - .catch(should.fail); - }); - - it('should support promises', function() { - var user = { email: 'foo@bar.com' }; - var model = { - getUserFromClient: function() { return Promise.resolve(user); }, - saveToken: function() {} - }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - grantType.getUserFromClient(request, {}).should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var user = { email: 'foo@bar.com' }; - var model = { - getUserFromClient: function() {return user; }, - saveToken: function() {} - }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - grantType.getUserFromClient(request, {}).should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function() { - var user = { email: 'foo@bar.com' }; - var model = { - getUserFromClient: function(userId, callback) { callback(null, user); }, - saveToken: function() {} - }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - grantType.getUserFromClient(request, {}).should.be.an.instanceOf(Promise); - }); - }); - - describe('saveToken()', function() { - it('should save the token', function() { - var token = {}; - var model = { - getUserFromClient: function() {}, - saveToken: function() { return token; }, - validateScope: function() { return 'foo'; } - }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 123, model: model }); - - return grantType.saveToken(token) - .then(function(data) { - data.should.equal(token); - }) - .catch(should.fail); - }); - - it('should support promises', function() { - var token = {}; - var model = { - getUserFromClient: function() {}, - saveToken: function() { return Promise.resolve(token); } - }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.saveToken(token).should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var token = {}; - var model = { - getUserFromClient: function() {}, - saveToken: function() { return token; } - }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.saveToken(token).should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function() { - var token = {}; - var model = { - getUserFromClient: function() {}, - saveToken: function(tokenToSave, client, user, callback) { callback(null, token); } - }; - var grantType = new ClientCredentialsGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.saveToken(token).should.be.an.instanceOf(Promise); - }); - }); -}); diff --git a/test/integration/grant-types/implicit-grant-type.spec.ts b/test/integration/grant-types/implicit-grant-type.spec.ts new file mode 100644 index 000000000..0af699fb1 --- /dev/null +++ b/test/integration/grant-types/implicit-grant-type.spec.ts @@ -0,0 +1,269 @@ +import * as should from 'should'; +import { InvalidArgumentError } from '../../../lib/errors'; +import { ImplicitGrantType } from '../../../lib/grant-types'; +import { Request } from '../../../lib/request'; + +/** + * Test `ImplicitGrantType` integration. + */ + +describe('ImplicitGrantType integration', () => { + describe('constructor()', () => { + it('should throw an error if `model` is missing', () => { + try { + new ImplicitGrantType({ accessTokenLifetime: 3600 }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `model`'); + } + }); + + it('should throw an error if the model does not implement `saveToken()`', () => { + try { + const model = {}; + + new ImplicitGrantType({ model, accessTokenLifetime: 3600 }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `saveToken()`', + ); + } + }); + + it('should throw an error if the `user` parameter is missing', () => { + try { + const model = { + saveToken() {}, + }; + + new ImplicitGrantType({ model, accessTokenLifetime: 3600 }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `user`'); + } + }); + }); + + describe('handle()', () => { + it('should throw an error if `request` is missing', async () => { + const model = { + saveToken() {}, + }; + const grantType: any = new ImplicitGrantType({ + accessTokenLifetime: 123, + model, + user: {}, + }); + + try { + await grantType.handle(); + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `request`'); + } + }); + + it('should throw an error if `client` is missing', async () => { + const model = { + saveToken() {}, + }; + const grantType: any = new ImplicitGrantType({ + accessTokenLifetime: 123, + model, + user: {}, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + await grantType.handle(request, undefined); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `client`'); + } + }); + + it('should return a token', () => { + const client = { id: 'foobar' }; + const token = { accessToken: 'foobar-token' }; + const model = { + saveToken() { + return token; + }, + validateScope() { + return 'foo'; + }, + }; + const grantType: any = new ImplicitGrantType({ + accessTokenLifetime: 123, + model, + user: {}, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return grantType + .handle(request, client) + .then(data => { + data.should.equal(token); + }) + .catch(should.fail); + }); + + it('should support promises', () => { + const client = { id: 'foobar' }; + const model = { + saveToken() {}, + }; + const grantType: any = new ImplicitGrantType({ + accessTokenLifetime: 123, + model, + user: {}, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + + grantType.handle(request, client).should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const client = { id: 'foobar' }; + const model = { + saveToken() {}, + }; + const grantType: any = new ImplicitGrantType({ + accessTokenLifetime: 123, + model, + user: {}, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + + grantType.handle(request, client).should.be.an.instanceOf(Promise); + }); + + // it('should support callbacks', () => { + // const client = { id: 'foobar' }; + // const model = { + // saveToken(tokenToSave, client, user, callback) { + // callback(null, tokenToSave); + // }, + // }; + // const grantType:any = new ImplicitGrantType({ + // accessTokenLifetime: 123, + // model, + // user: {}, + // }); + // const request = new Request({ + // body: { code: 12345 }, + // headers: {}, + // method: 'ANY', + // query: {}, + // }); + + // grantType.handle(request, client).should.be.an.instanceOf(Promise); + // grantType.handle(request, client).then(data => { + // data.should.have.keys('accessToken', 'accessTokenExpiresAt'); + // data.accessToken.should.be.type('string'); + // }); + // }); + }); + + describe('saveToken()', () => { + it('should save the token', () => { + const token = {}; + const model = { + saveToken() { + return token; + }, + validateScope() { + return 'foo'; + }, + }; + const grantType: any = new ImplicitGrantType({ + accessTokenLifetime: 123, + model, + user: {}, + }); + + return grantType + .saveToken(token) + .then(data => { + data.should.equal(token); + }) + .catch(should.fail); + }); + + it('should support promises', () => { + const token = {}; + const model = { + saveToken() { + return Promise.resolve(token); + }, + }; + const grantType: any = new ImplicitGrantType({ + accessTokenLifetime: 123, + model, + user: {}, + }); + + grantType.saveToken(token).should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const token = {}; + const model = { + saveToken() { + return token; + }, + }; + const grantType: any = new ImplicitGrantType({ + accessTokenLifetime: 123, + model, + user: {}, + }); + + grantType.saveToken(token).should.be.an.instanceOf(Promise); + }); + + // it('should support callbacks', () => { + // const token = {}; + // const model = { + // saveToken(tokenToSave, client, user, callback) { + // callback(null, token); + // }, + // }; + // const grantType:any = new ImplicitGrantType({ + // accessTokenLifetime: 123, + // model, + // user: {}, + // }); + + // grantType.saveToken(token).should.be.an.instanceOf(Promise); + // }); + }); +}); diff --git a/test/integration/grant-types/password-grant-type.spec.ts b/test/integration/grant-types/password-grant-type.spec.ts new file mode 100755 index 000000000..a02b37729 --- /dev/null +++ b/test/integration/grant-types/password-grant-type.spec.ts @@ -0,0 +1,506 @@ +import * as should from 'should'; +import { + InvalidArgumentError, + InvalidGrantError, + InvalidRequestError, +} from '../../../lib/errors'; +import { PasswordGrantType } from '../../../lib/grant-types'; +import { Request } from '../../../lib/request'; + +/** + * Test `PasswordGrantType` integration. + */ + +describe('PasswordGrantType integration', () => { + describe('constructor()', () => { + it('should throw an error if `model` is missing', () => { + try { + new PasswordGrantType({ accessTokenLifetime: 3600 }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `model`'); + } + }); + + it('should throw an error if the model does not implement `getUser()`', () => { + try { + new PasswordGrantType({ accessTokenLifetime: 3600, model: {} }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `getUser()`', + ); + } + }); + + it('should throw an error if the model does not implement `saveToken()`', () => { + try { + const model = { + getUser: () => {}, + }; + + new PasswordGrantType({ accessTokenLifetime: 3600, model }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `saveToken()`', + ); + } + }); + }); + + describe('handle()', () => { + it('should throw an error if `request` is missing', async () => { + const model = { + getUser: () => {}, + saveToken: () => {}, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + + try { + await grantType.handle(undefined, undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `request`'); + } + }); + + it('should throw an error if `client` is missing', async () => { + const model = { + getUser: () => {}, + saveToken: () => {}, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + + try { + await grantType.handle({}, undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `client`'); + } + }); + + it('should return a token', () => { + const client = { id: 'foobar' }; + const token = {}; + const model = { + getUser: () => { + return {}; + }, + saveToken: () => { + return token; + }, + validateScope: () => { + return 'baz'; + }, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { username: 'foo', password: 'bar', scope: 'baz' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return grantType + .handle(request, client) + .then(data => { + data.should.equal(token); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + + it('should support promises', () => { + const client = { id: 'foobar' }; + const token = {}; + const model = { + getUser() { + return {}; + }, + saveToken() { + return Promise.resolve(token); + }, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { username: 'foo', password: 'bar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + grantType.handle(request, client).should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const client = { id: 'foobar' }; + const token = {}; + const model = { + getUser() { + return {}; + }, + saveToken() { + return token; + }, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { username: 'foo', password: 'bar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + grantType.handle(request, client).should.be.an.instanceOf(Promise); + }); + + /* it('should support callbacks', () => { + const client = { id: 'foobar' }; + const token = {}; + const model = { + getUser(username, password, callback) { + callback(null, {}); + }, + saveToken(tokenToSave, client, user, callback) { + callback(null, token); + }, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { username: 'foo', password: 'bar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + grantType.handle(request, client).should.be.an.instanceOf(Promise); + }); */ + }); + + describe('getUser()', () => { + it('should throw an error if the request body does not contain `username`', async () => { + const model = { + getUser() {}, + saveToken() {}, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + await grantType.getUser(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Missing parameter: `username`'); + } + }); + + it('should throw an error if the request body does not contain `password`', async () => { + const model = { + getUser() {}, + saveToken() {}, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { username: 'foo' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + await grantType.getUser(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Missing parameter: `password`'); + } + }); + + it('should throw an error if `username` is invalid', async () => { + const model = { + getUser() {}, + saveToken() {}, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { username: '\r\n', password: 'foobar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + await grantType.getUser(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Invalid parameter: `username`'); + } + }); + + it('should throw an error if `password` is invalid', async () => { + const model = { + getUser() {}, + saveToken() {}, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { username: 'foobar', password: '\r\n' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + await grantType.getUser(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Invalid parameter: `password`'); + } + }); + + it('should throw an error if `user` is missing', async () => { + const model = { + getUser() {}, + saveToken() {}, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { username: 'foo', password: 'bar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await grantType.getUser(request); + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidGrantError); + e.message.should.equal('Invalid grant: user credentials are invalid'); + } + }); + + it('should return a user', async () => { + const user = { email: 'foo@bar.com' }; + const model = { + getUser() { + return user; + }, + saveToken() {}, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { username: 'foo', password: 'bar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + const data = await grantType.getUser(request); + data.should.equal(user); + } catch (error) { + should.fail('should.fail', ''); + } + }); + + it('should support promises', () => { + const user = { email: 'foo@bar.com' }; + const model = { + getUser() { + return Promise.resolve(user); + }, + saveToken() {}, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { username: 'foo', password: 'bar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + grantType.getUser(request).should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const user = { email: 'foo@bar.com' }; + const model = { + getUser() { + return user; + }, + saveToken() {}, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { username: 'foo', password: 'bar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + grantType.getUser(request).should.be.an.instanceOf(Promise); + }); + + /* it('should support callbacks', () => { + const user = { email: 'foo@bar.com' }; + const model = { + getUser(username, password, callback) { + callback(null, user); + }, + saveToken() {}, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { username: 'foo', password: 'bar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + grantType.getUser(request).should.be.an.instanceOf(Promise); + }); */ + }); + + describe('saveToken()', () => { + it('should save the token', async () => { + const token: any = {}; + const model = { + getUser() {}, + saveToken() { + return token; + }, + validateScope() { + return 'foo'; + }, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + try { + const data = await grantType.saveToken({}, {} as any, token); + data.should.equal(token); + } catch (error) { + should.fail('should.fail', ''); + } + }); + + it('should support promises', () => { + const token: any = {}; + const model = { + getUser() {}, + saveToken() { + return Promise.resolve(token); + }, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + + grantType + .saveToken({}, {} as any, token) + .should.be.an.instanceOf(Promise); + }); + + /* it('should support non-promises', () => { + const token = {}; + const model = { + getUser() {}, + saveToken() { + return token; + }, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + + grantType.saveToken(token).should.be.an.instanceOf(Promise); + }); */ + + /* it('should support callbacks', () => { + const token = {}; + const model = { + getUser() {}, + saveToken(tokenToSave, client, user, callback) { + callback(null, token); + }, + }; + const grantType = new PasswordGrantType({ + accessTokenLifetime: 123, + model, + }); + + grantType.saveToken({}, {}, token).should.be.an.instanceOf(Promise); + }); */ + }); +}); diff --git a/test/integration/grant-types/password-grant-type_test.js b/test/integration/grant-types/password-grant-type_test.js deleted file mode 100644 index 9cdf50dd5..000000000 --- a/test/integration/grant-types/password-grant-type_test.js +++ /dev/null @@ -1,344 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var InvalidArgumentError = require('../../../lib/errors/invalid-argument-error'); -var InvalidGrantError = require('../../../lib/errors/invalid-grant-error'); -var InvalidRequestError = require('../../../lib/errors/invalid-request-error'); -var PasswordGrantType = require('../../../lib/grant-types/password-grant-type'); -var Promise = require('bluebird'); -var Request = require('../../../lib/request'); -var should = require('should'); - -/** - * Test `PasswordGrantType` integration. - */ - -describe('PasswordGrantType integration', function() { - describe('constructor()', function() { - it('should throw an error if `model` is missing', function() { - try { - new PasswordGrantType(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `model`'); - } - }); - - it('should throw an error if the model does not implement `getUser()`', function() { - try { - new PasswordGrantType({ model: {} }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: model does not implement `getUser()`'); - } - }); - - it('should throw an error if the model does not implement `saveToken()`', function() { - try { - var model = { - getUser: function() {} - }; - - new PasswordGrantType({ model: model }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: model does not implement `saveToken()`'); - } - }); - }); - - describe('handle()', function() { - it('should throw an error if `request` is missing', function() { - var model = { - getUser: function() {}, - saveToken: function() {} - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - - try { - grantType.handle(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `request`'); - } - }); - - it('should throw an error if `client` is missing', function() { - var model = { - getUser: function() {}, - saveToken: function() {} - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - - try { - grantType.handle({}); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `client`'); - } - }); - - it('should return a token', function() { - var client = { id: 'foobar' }; - var token = {}; - var model = { - getUser: function() { return {}; }, - saveToken: function() { return token; }, - validateScope: function() { return 'baz'; } - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { username: 'foo', password: 'bar', scope: 'baz' }, headers: {}, method: {}, query: {} }); - - return grantType.handle(request, client) - .then(function(data) { - data.should.equal(token); - }) - .catch(should.fail); - }); - - it('should support promises', function() { - var client = { id: 'foobar' }; - var token = {}; - var model = { - getUser: function() { return {}; }, - saveToken: function() { return Promise.resolve(token); } - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { username: 'foo', password: 'bar' }, headers: {}, method: {}, query: {} }); - - grantType.handle(request, client).should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var client = { id: 'foobar' }; - var token = {}; - var model = { - getUser: function() { return {}; }, - saveToken: function() { return token; } - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { username: 'foo', password: 'bar' }, headers: {}, method: {}, query: {} }); - - grantType.handle(request, client).should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function() { - var client = { id: 'foobar' }; - var token = {}; - var model = { - getUser: function(username, password, callback) { callback(null, {}); }, - saveToken: function(tokenToSave, client, user, callback) { callback(null, token); } - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { username: 'foo', password: 'bar' }, headers: {}, method: {}, query: {} }); - - grantType.handle(request, client).should.be.an.instanceOf(Promise); - }); - }); - - describe('getUser()', function() { - it('should throw an error if the request body does not contain `username`', function() { - var model = { - getUser: function() {}, - saveToken: function() {} - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - try { - grantType.getUser(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Missing parameter: `username`'); - } - }); - - it('should throw an error if the request body does not contain `password`', function() { - var model = { - getUser: function() {}, - saveToken: function() {} - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { username: 'foo' }, headers: {}, method: {}, query: {} }); - - try { - grantType.getUser(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Missing parameter: `password`'); - } - }); - - it('should throw an error if `username` is invalid', function() { - var model = { - getUser: function() {}, - saveToken: function() {} - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { username: '\r\n', password: 'foobar' }, headers: {}, method: {}, query: {} }); - - try { - grantType.getUser(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Invalid parameter: `username`'); - } - }); - - it('should throw an error if `password` is invalid', function() { - var model = { - getUser: function() {}, - saveToken: function() {} - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { username: 'foobar', password: '\r\n' }, headers: {}, method: {}, query: {} }); - - try { - grantType.getUser(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Invalid parameter: `password`'); - } - }); - - it('should throw an error if `user` is missing', function() { - var model = { - getUser: function() {}, - saveToken: function() {} - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { username: 'foo', password: 'bar' }, headers: {}, method: {}, query: {} }); - - return grantType.getUser(request) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidGrantError); - e.message.should.equal('Invalid grant: user credentials are invalid'); - }); - }); - - it('should return a user', function() { - var user = { email: 'foo@bar.com' }; - var model = { - getUser: function() { return user; }, - saveToken: function() {} - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { username: 'foo', password: 'bar' }, headers: {}, method: {}, query: {} }); - - return grantType.getUser(request) - .then(function(data) { - data.should.equal(user); - }) - .catch(should.fail); - }); - - it('should support promises', function() { - var user = { email: 'foo@bar.com' }; - var model = { - getUser: function() { return Promise.resolve(user); }, - saveToken: function() {} - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { username: 'foo', password: 'bar' }, headers: {}, method: {}, query: {} }); - - grantType.getUser(request).should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var user = { email: 'foo@bar.com' }; - var model = { - getUser: function() { return user; }, - saveToken: function() {} - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { username: 'foo', password: 'bar' }, headers: {}, method: {}, query: {} }); - - grantType.getUser(request).should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function() { - var user = { email: 'foo@bar.com' }; - var model = { - getUser: function(username, password, callback) { callback(null, user); }, - saveToken: function() {} - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { username: 'foo', password: 'bar' }, headers: {}, method: {}, query: {} }); - - grantType.getUser(request).should.be.an.instanceOf(Promise); - }); - }); - - describe('saveToken()', function() { - it('should save the token', function() { - var token = {}; - var model = { - getUser: function() {}, - saveToken: function() { return token; }, - validateScope: function() { return 'foo'; } - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - - return grantType.saveToken(token) - .then(function(data) { - data.should.equal(token); - }) - .catch(should.fail); - }); - - it('should support promises', function() { - var token = {}; - var model = { - getUser: function() {}, - saveToken: function() { return Promise.resolve(token); } - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.saveToken(token).should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var token = {}; - var model = { - getUser: function() {}, - saveToken: function() { return token; } - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.saveToken(token).should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function() { - var token = {}; - var model = { - getUser: function() {}, - saveToken: function(tokenToSave, client, user, callback) { callback(null, token); } - }; - var grantType = new PasswordGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.saveToken(token).should.be.an.instanceOf(Promise); - }); - }); -}); diff --git a/test/integration/grant-types/refresh-token-grant-type.spec.ts b/test/integration/grant-types/refresh-token-grant-type.spec.ts new file mode 100755 index 000000000..c37ecb9e0 --- /dev/null +++ b/test/integration/grant-types/refresh-token-grant-type.spec.ts @@ -0,0 +1,864 @@ +import * as should from 'should'; +import { + InvalidArgumentError, + InvalidGrantError, + InvalidRequestError, + ServerError, +} from '../../../lib/errors'; +import { RefreshTokenGrantType } from '../../../lib/grant-types'; +import { Request } from '../../../lib/request'; + +/** + * Test `RefreshTokenGrantType` integration. + */ + +describe('RefreshTokenGrantType integration', () => { + describe('constructor()', () => { + it('should throw an error if `model` is missing', () => { + try { + new RefreshTokenGrantType({ accessTokenLifetime: 3600 }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `model`'); + } + }); + + it('should throw an error if the model does not implement `getRefreshToken()`', () => { + try { + new RefreshTokenGrantType({ accessTokenLifetime: 3600, model: {} }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `getRefreshToken()`', + ); + } + }); + + it('should throw an error if the model does not implement `revokeToken()`', () => { + try { + const model = { + getRefreshToken() {}, + }; + + new RefreshTokenGrantType({ accessTokenLifetime: 3600, model }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `revokeToken()`', + ); + } + }); + + it('should throw an error if the model does not implement `saveToken()`', () => { + try { + const model = { + getRefreshToken() {}, + revokeToken() {}, + }; + + new RefreshTokenGrantType({ accessTokenLifetime: 3600, model }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `saveToken()`', + ); + } + }); + }); + + describe('handle()', () => { + it('should throw an error if `request` is missing', async () => { + const model = { + getRefreshToken: () => {}, + revokeToken: () => {}, + saveToken: () => {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + + try { + await grantType.handle(undefined, undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `request`'); + } + }); + + it('should throw an error if `client` is missing', async () => { + const model = { + getRefreshToken: () => {}, + revokeToken: () => {}, + saveToken: () => {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + await grantType.handle(request, undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `client`'); + } + }); + + it('should return a token', () => { + const client: any = { id: 123 }; + const token: any = { accessToken: 'foo', client: { id: 123 }, user: {} }; + const model = { + getRefreshToken: () => { + return token; + }, + revokeToken: () => { + return { + accessToken: 'foo', + client: { id: 123 }, + refreshTokenExpiresAt: new Date(new Date().getTime() / 2), + user: {}, + }; + }, + saveToken: () => { + return token; + }, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { refresh_token: 'foobar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return grantType + .handle(request, client) + .then(data => { + data.should.equal(token); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + + it('should support promises', () => { + const client: any = { id: 123 }; + const model = { + getRefreshToken() { + return Promise.resolve({ + accessToken: 'foo', + client: { id: 123 }, + user: {}, + }); + }, + revokeToken() { + return Promise.resolve({ + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date().getTime() / 2), + user: {}, + }); + }, + saveToken() { + return Promise.resolve({ accessToken: 'foo', client: {}, user: {} }); + }, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { refresh_token: 'foobar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + grantType.handle(request, client).should.be.an.instanceOf(Promise); + }); + + /* it('should support non-promises', () => { + const client : any= { id: 123 }; + const model = { + getRefreshToken() { + return { accessToken: 'foo', client: { id: 123 }, user: {} }; + }, + revokeToken() { + return { + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date() / 2), + user: {}, + }; + }, + saveToken() { + return { accessToken: 'foo', client: {}, user: {} }; + }, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { refresh_token: 'foobar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + grantType.handle(request, client).should.be.an.instanceOf(Promise); + }); */ + + /* it('should support callbacks', () => { + const client : any= { id: 123 }; + const model = { + getRefreshToken(refreshToken, callback) { + callback(null, { accessToken: 'foo', client: { id: 123 }, user: {} }); + }, + revokeToken(refreshToken, callback) { + callback(null, { + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date() / 2), + user: {}, + }); + }, + saveToken(tokenToSave, client, user, callback) { + callback(null, { accessToken: 'foo', client: {}, user: {} }); + }, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { refresh_token: 'foobar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + grantType.handle(request, client).should.be.an.instanceOf(Promise); + }); */ + }); + + describe('getRefreshToken()', () => { + it('should throw an error if the `refreshToken` parameter is missing from the request body', async () => { + const client: any = {}; + const model = { + getRefreshToken: () => {}, + revokeToken: () => {}, + saveToken: () => {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + await grantType.getRefreshToken(request, client); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Missing parameter: `refresh_token`'); + } + }); + + it('should throw an error if `refreshToken` is not found', () => { + const client: any = { id: 123 }; + const model = { + getRefreshToken() { + return; + }, + revokeToken() {}, + saveToken() {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: { refresh_token: '12345' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return grantType + .getRefreshToken(request, client) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(InvalidGrantError); + e.message.should.equal('Invalid grant: refresh token is invalid'); + }); + }); + + it('should throw an error if `refreshToken.client` is missing', () => { + const client: any = {}; + const model = { + getRefreshToken() { + return {}; + }, + revokeToken() {}, + saveToken() {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: { refresh_token: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return grantType + .getRefreshToken(request, client) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(ServerError); + e.message.should.equal( + 'Server error: `getRefreshToken()` did not return a `client` object', + ); + }); + }); + + it('should throw an error if `refreshToken.user` is missing', () => { + const client: any = {}; + const model = { + getRefreshToken() { + return { accessToken: 'foo', client: {} }; + }, + revokeToken() {}, + saveToken() {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: { refresh_token: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return grantType + .getRefreshToken(request, client) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(ServerError); + e.message.should.equal( + 'Server error: `getRefreshToken()` did not return a `user` object', + ); + }); + }); + + it('should throw an error if the client id does not match', () => { + const client: any = { id: 123 }; + const model = { + getRefreshToken() { + return { accessToken: 'foo', client: { id: 456 }, user: {} }; + }, + revokeToken() {}, + saveToken() {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: { refresh_token: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return grantType + .getRefreshToken(request, client) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(InvalidGrantError); + e.message.should.equal('Invalid grant: refresh token is invalid'); + }); + }); + + it('should throw an error if `refresh_token` contains invalid characters', async () => { + const client: any = {}; + const model = { + getRefreshToken() { + return { client: { id: 456 }, user: {} }; + }, + revokeToken() {}, + saveToken() {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: { refresh_token: 'øå€£‰' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + await grantType.getRefreshToken(request, client); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Invalid parameter: `refresh_token`'); + } + }); + + it('should throw an error if `refresh_token` is missing', () => { + const client: any = {}; + const model = { + getRefreshToken() { + return { accessToken: 'foo', client: { id: 456 }, user: {} }; + }, + revokeToken() {}, + saveToken() {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: { refresh_token: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return grantType + .getRefreshToken(request, client) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(InvalidGrantError); + e.message.should.equal('Invalid grant: refresh token is invalid'); + }); + }); + + it('should throw an error if `refresh_token` is expired', () => { + const client: any = { id: 123 }; + const date = new Date(new Date().getTime() / 2); + const model = { + getRefreshToken() { + return { + accessToken: 'foo', + client: { id: 123 }, + refreshTokenExpiresAt: date, + user: {}, + }; + }, + revokeToken() {}, + saveToken() {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: { refresh_token: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return grantType + .getRefreshToken(request, client) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(InvalidGrantError); + e.message.should.equal('Invalid grant: refresh token has expired'); + }); + }); + + it('should throw an error if `refreshTokenExpiresAt` is not a date value', () => { + const client: any = { id: 123 }; + const model = { + getRefreshToken() { + return { + accessToken: 'foo', + client: { id: 123 }, + refreshTokenExpiresAt: 'stringValue', + user: {}, + }; + }, + revokeToken() {}, + saveToken() {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: { refresh_token: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return grantType + .getRefreshToken(request, client) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(ServerError); + e.message.should.equal( + 'Server error: `refreshTokenExpiresAt` must be a Date instance', + ); + }); + }); + + it('should return a token', () => { + const client: any = { id: 123 }; + const token: any = { accessToken: 'foo', client: { id: 123 }, user: {} }; + const model = { + getRefreshToken() { + return token; + }, + revokeToken() {}, + saveToken() {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { refresh_token: 'foobar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return grantType + .getRefreshToken(request, client) + .then(data => { + data.should.equal(token); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + + it('should support promises', () => { + const client: any = { id: 123 }; + const token: any = { accessToken: 'foo', client: { id: 123 }, user: {} }; + const model = { + getRefreshToken() { + return Promise.resolve(token); + }, + revokeToken() {}, + saveToken() {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { refresh_token: 'foobar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + grantType + .getRefreshToken(request, client) + .should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const client: any = { id: 123 }; + const token: any = { accessToken: 'foo', client: { id: 123 }, user: {} }; + const model = { + getRefreshToken() { + return token; + }, + revokeToken() {}, + saveToken() {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + const request = new Request({ + body: { refresh_token: 'foobar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + grantType + .getRefreshToken(request, client) + .should.be.an.instanceOf(Promise); + }); + + // it('should support callbacks', () => { + // const client : any= { id: 123 }; + // const token : any= { accessToken: 'foo', client: { id: 123 }, user: {} }; + // const model = { + // getRefreshToken(refreshToken, callback) { + // callback(undefined, token); + // }, + // revokeToken() {}, + // saveToken() {}, + // }; + // const grantType = new RefreshTokenGrantType({ + // accessTokenLifetime: 123, + // model, + // }); + // const request = new Request({ + // body: { refresh_token: 'foobar' }, + // headers: {}, + // method: 'ANY', + // query: {}, + // }); + + // grantType + // .getRefreshToken(request, client) + // .should.be.an.instanceOf(Promise); + // }); + }); + + describe('revokeToken()', () => { + it('should throw an error if the `token` is invalid', () => { + const model = { + getRefreshToken() {}, + revokeToken() {}, + saveToken() {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + + grantType + .revokeToken({} as any) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(InvalidGrantError); + e.message.should.equal('Invalid grant: refresh token is invalid'); + }); + }); + + it('should revoke the token', () => { + const token: any = { + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date().getTime() / 2), + user: {}, + }; + const model = { + getRefreshToken() {}, + revokeToken() { + return token; + }, + saveToken() {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + + return grantType + .revokeToken(token) + .then(data => { + data.should.equal(token); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + + it('should support promises', () => { + const token: any = { + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date().getTime() / 2), + user: {}, + }; + const model = { + getRefreshToken() {}, + revokeToken() { + return Promise.resolve(token); + }, + saveToken() {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + + grantType.revokeToken(token).should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const token: any = { + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date().getTime() / 2), + user: {}, + }; + const model = { + getRefreshToken() {}, + revokeToken() { + return token; + }, + saveToken() {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + + grantType.revokeToken(token).should.be.an.instanceOf(Promise); + }); + + /* it('should support callbacks', () => { + const token : any= { + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date().getTime() / 2), + user: {}, + }; + const model = { + getRefreshToken() {}, + revokeToken(refreshToken, callback) { + callback(undefined, token); + }, + saveToken() {}, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + + grantType.revokeToken(token).should.be.an.instanceOf(Promise); + }); */ + }); + + describe('saveToken()', () => { + it('should save the token', async () => { + const token: any = {}; + const model = { + getRefreshToken() {}, + revokeToken() {}, + saveToken() { + return token; + }, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + try { + const data = await grantType.saveToken({}, {} as any, token); + data.should.equal(token); + } catch (error) { + should.fail('should.fail', ''); + } + }); + + it('should support promises', () => { + const token: any = {}; + const model = { + getRefreshToken() {}, + revokeToken() {}, + saveToken() { + return Promise.resolve(token); + }, + }; + const grantType = new RefreshTokenGrantType({ + accessTokenLifetime: 123, + model, + }); + + grantType + .saveToken({}, {} as any, token) + .should.be.an.instanceOf(Promise); + }); + + // it('should support non-promises', () => { + // const token : any= {}; + // const model = { + // getRefreshToken() {}, + // revokeToken() {}, + // saveToken() { + // return token; + // }, + // }; + // const grantType = new RefreshTokenGrantType({ + // accessTokenLifetime: 123, + // model, + // }); + + // grantType.saveToken(token).should.be.an.instanceOf(Promise); + // }); + + // it('should support callbacks', () => { + // const token : any= {}; + // const model = { + // getRefreshToken() {}, + // revokeToken() {}, + // saveToken(tokenToSave, client, user, callback) { + // callback(null, token); + // }, + // }; + // const grantType = new RefreshTokenGrantType({ + // accessTokenLifetime: 123, + // model, + // }); + + // grantType.saveToken(token).should.be.an.instanceOf(Promise); + // }); + }); +}); diff --git a/test/integration/grant-types/refresh-token-grant-type_test.js b/test/integration/grant-types/refresh-token-grant-type_test.js deleted file mode 100644 index 925396afe..000000000 --- a/test/integration/grant-types/refresh-token-grant-type_test.js +++ /dev/null @@ -1,536 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var InvalidArgumentError = require('../../../lib/errors/invalid-argument-error'); -var InvalidGrantError = require('../../../lib/errors/invalid-grant-error'); -var InvalidRequestError = require('../../../lib/errors/invalid-request-error'); -var Promise = require('bluebird'); -var RefreshTokenGrantType = require('../../../lib/grant-types/refresh-token-grant-type'); -var Request = require('../../../lib/request'); -var ServerError = require('../../../lib/errors/server-error'); -var should = require('should'); - -/** - * Test `RefreshTokenGrantType` integration. - */ - -describe('RefreshTokenGrantType integration', function() { - describe('constructor()', function() { - it('should throw an error if `model` is missing', function() { - try { - new RefreshTokenGrantType(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `model`'); - } - }); - - it('should throw an error if the model does not implement `getRefreshToken()`', function() { - try { - new RefreshTokenGrantType({ model: {} }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: model does not implement `getRefreshToken()`'); - } - }); - - it('should throw an error if the model does not implement `revokeToken()`', function() { - try { - var model = { - getRefreshToken: function() {} - }; - - new RefreshTokenGrantType({ model: model }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: model does not implement `revokeToken()`'); - } - }); - - it('should throw an error if the model does not implement `saveToken()`', function() { - try { - var model = { - getRefreshToken: function() {}, - revokeToken: function() {} - }; - - new RefreshTokenGrantType({ model: model }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: model does not implement `saveToken()`'); - } - }); - }); - - describe('handle()', function() { - it('should throw an error if `request` is missing', function() { - var model = { - getRefreshToken: function() {}, - revokeToken: function() {}, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - - try { - grantType.handle(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `request`'); - } - }); - - it('should throw an error if `client` is missing', function() { - var model = { - getRefreshToken: function() {}, - revokeToken: function() {}, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - try { - grantType.handle(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `client`'); - } - }); - - it('should return a token', function() { - var client = { id: 123 }; - var token = { accessToken: 'foo', client: { id: 123 }, user: {} }; - var model = { - getRefreshToken: function() { return token; }, - revokeToken: function() { return { accessToken: 'foo', client: { id: 123 }, refreshTokenExpiresAt: new Date(new Date() / 2), user: {} }; }, - saveToken: function() { return token; } - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { refresh_token: 'foobar' }, headers: {}, method: {}, query: {} }); - - return grantType.handle(request, client) - .then(function(data) { - data.should.equal(token); - }) - .catch(should.fail); - }); - - it('should support promises', function() { - var client = { id: 123 }; - var model = { - getRefreshToken: function() { return Promise.resolve({ accessToken: 'foo', client: { id: 123 }, user: {} }); }, - revokeToken: function() { return Promise.resolve({ accessToken: 'foo', client: {}, refreshTokenExpiresAt: new Date(new Date() / 2), user: {} }); }, - saveToken: function() { return Promise.resolve({ accessToken: 'foo', client: {}, user: {} }); } - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { refresh_token: 'foobar' }, headers: {}, method: {}, query: {} }); - - grantType.handle(request, client).should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var client = { id: 123 }; - var model = { - getRefreshToken: function() { return { accessToken: 'foo', client: { id: 123 }, user: {} }; }, - revokeToken: function() { return { accessToken: 'foo', client: {}, refreshTokenExpiresAt: new Date(new Date() / 2), user: {} }; }, - saveToken: function() { return { accessToken: 'foo', client: {}, user: {} }; } - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { refresh_token: 'foobar' }, headers: {}, method: {}, query: {} }); - - grantType.handle(request, client).should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function() { - var client = { id: 123 }; - var model = { - getRefreshToken: function(refreshToken, callback) { callback(null, { accessToken: 'foo', client: { id: 123 }, user: {} }); }, - revokeToken: function(refreshToken, callback) { callback(null, { accessToken: 'foo', client: {}, refreshTokenExpiresAt: new Date(new Date() / 2), user: {} }); }, - saveToken: function(tokenToSave, client, user, callback) { callback(null,{ accessToken: 'foo', client: {}, user: {} }); } - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { refresh_token: 'foobar' }, headers: {}, method: {}, query: {} }); - - grantType.handle(request, client).should.be.an.instanceOf(Promise); - }); - }); - - describe('getRefreshToken()', function() { - it('should throw an error if the `refreshToken` parameter is missing from the request body', function() { - var client = {}; - var model = { - getRefreshToken: function() {}, - revokeToken: function() {}, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - try { - grantType.getRefreshToken(request, client); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Missing parameter: `refresh_token`'); - } - }); - - it('should throw an error if `refreshToken` is not found', function() { - var client = { id: 123 }; - var model = { - getRefreshToken: function() { return; }, - revokeToken: function() {}, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: { refresh_token: '12345' }, headers: {}, method: {}, query: {} }); - - return grantType.getRefreshToken(request, client) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidGrantError); - e.message.should.equal('Invalid grant: refresh token is invalid'); - }); - }); - - it('should throw an error if `refreshToken.client` is missing', function() { - var client = {}; - var model = { - getRefreshToken: function() { return {}; }, - revokeToken: function() {}, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: { refresh_token: 12345 }, headers: {}, method: {}, query: {} }); - - return grantType.getRefreshToken(request, client) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(ServerError); - e.message.should.equal('Server error: `getRefreshToken()` did not return a `client` object'); - }); - }); - - it('should throw an error if `refreshToken.user` is missing', function() { - var client = {}; - var model = { - getRefreshToken: function() { - return { accessToken: 'foo', client: {} }; - }, - revokeToken: function() {}, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: { refresh_token: 12345 }, headers: {}, method: {}, query: {} }); - - return grantType.getRefreshToken(request, client) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(ServerError); - e.message.should.equal('Server error: `getRefreshToken()` did not return a `user` object'); - }); - }); - - it('should throw an error if the client id does not match', function() { - var client = { id: 123 }; - var model = { - getRefreshToken: function() { - return { accessToken: 'foo', client: { id: 456 }, user: {} }; - }, - revokeToken: function() {}, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: { refresh_token: 12345 }, headers: {}, method: {}, query: {} }); - - return grantType.getRefreshToken(request, client) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidGrantError); - e.message.should.equal('Invalid grant: refresh token is invalid'); - }); - }); - - it('should throw an error if `refresh_token` contains invalid characters', function() { - var client = {}; - var model = { - getRefreshToken: function() { - return { client: { id: 456 }, user: {} }; - }, - revokeToken: function() {}, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: { refresh_token: 'øå€£‰' }, headers: {}, method: {}, query: {} }); - - try { - grantType.getRefreshToken(request, client); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Invalid parameter: `refresh_token`'); - } - }); - - it('should throw an error if `refresh_token` is missing', function() { - var client = {}; - var model = { - getRefreshToken: function() { - return { accessToken: 'foo', client: { id: 456 }, user: {} }; - }, - revokeToken: function() {}, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: { refresh_token: 12345 }, headers: {}, method: {}, query: {} }); - - return grantType.getRefreshToken(request, client) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidGrantError); - e.message.should.equal('Invalid grant: refresh token is invalid'); - }); - }); - - it('should throw an error if `refresh_token` is expired', function() { - var client = { id: 123 }; - var date = new Date(new Date() / 2); - var model = { - getRefreshToken: function() { - return { accessToken: 'foo', client: { id: 123 }, refreshTokenExpiresAt: date, user: {} }; - }, - revokeToken: function() {}, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: { refresh_token: 12345 }, headers: {}, method: {}, query: {} }); - - return grantType.getRefreshToken(request, client) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidGrantError); - e.message.should.equal('Invalid grant: refresh token has expired'); - }); - }); - - it('should throw an error if `refreshTokenExpiresAt` is not a date value', function() { - var client = { id: 123 }; - var model = { - getRefreshToken: function() { - return { accessToken: 'foo', client: { id: 123 }, refreshTokenExpiresAt: 'stringvalue', user: {} }; - }, - revokeToken: function() {}, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: { refresh_token: 12345 }, headers: {}, method: {}, query: {} }); - - return grantType.getRefreshToken(request, client) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(ServerError); - e.message.should.equal('Server error: `refreshTokenExpiresAt` must be a Date instance'); - }); - }); - - it('should return a token', function() { - var client = { id: 123 }; - var token = { accessToken: 'foo', client: { id: 123 }, user: {} }; - var model = { - getRefreshToken: function() { return token; }, - revokeToken: function() {}, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { refresh_token: 'foobar' }, headers: {}, method: {}, query: {} }); - - return grantType.getRefreshToken(request, client) - .then(function(data) { - data.should.equal(token); - }) - .catch(should.fail); - }); - - it('should support promises', function() { - var client = { id: 123 }; - var token = { accessToken: 'foo', client: { id: 123 }, user: {} }; - var model = { - getRefreshToken: function() { return Promise.resolve(token); }, - revokeToken: function() {}, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { refresh_token: 'foobar' }, headers: {}, method: {}, query: {} }); - - grantType.getRefreshToken(request, client).should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var client = { id: 123 }; - var token = { accessToken: 'foo', client: { id: 123 }, user: {} }; - var model = { - getRefreshToken: function() { return token; }, - revokeToken: function() {}, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { refresh_token: 'foobar' }, headers: {}, method: {}, query: {} }); - - grantType.getRefreshToken(request, client).should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function() { - var client = { id: 123 }; - var token = { accessToken: 'foo', client: { id: 123 }, user: {} }; - var model = { - getRefreshToken: function(refreshToken, callback) { callback(null, token); }, - revokeToken: function() {}, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); - var request = new Request({ body: { refresh_token: 'foobar' }, headers: {}, method: {}, query: {} }); - - grantType.getRefreshToken(request, client).should.be.an.instanceOf(Promise); - }); - }); - - describe('revokeToken()', function() { - it('should throw an error if the `token` is invalid', function() { - var model = { - getRefreshToken: function() {}, - revokeToken: function() {}, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - - grantType.revokeToken({}) - .then(should.fail) - .catch(function (e) { - e.should.be.an.instanceOf(InvalidGrantError); - e.message.should.equal('Invalid grant: refresh token is invalid'); - }); - }); - - it('should revoke the token', function() { - var token = { accessToken: 'foo', client: {}, refreshTokenExpiresAt: new Date(new Date() / 2), user: {} }; - var model = { - getRefreshToken: function() {}, - revokeToken: function() { return token; }, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); - - return grantType.revokeToken(token) - .then(function(data) { - data.should.equal(token); - }) - .catch(should.fail); - }); - - it('should support promises', function() { - var token = { accessToken: 'foo', client: {}, refreshTokenExpiresAt: new Date(new Date() / 2), user: {} }; - var model = { - getRefreshToken: function() {}, - revokeToken: function() { return Promise.resolve(token); }, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.revokeToken(token).should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var token = { accessToken: 'foo', client: {}, refreshTokenExpiresAt: new Date(new Date() / 2), user: {} }; - var model = { - getRefreshToken: function() {}, - revokeToken: function() { return token; }, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.revokeToken(token).should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function() { - var token = { accessToken: 'foo', client: {}, refreshTokenExpiresAt: new Date(new Date() / 2), user: {} }; - var model = { - getRefreshToken: function() {}, - revokeToken: function(refreshToken, callback) { callback(null, token); }, - saveToken: function() {} - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.revokeToken(token).should.be.an.instanceOf(Promise); - }); - }); - - describe('saveToken()', function() { - it('should save the token', function() { - var token = {}; - var model = { - getRefreshToken: function() {}, - revokeToken: function() {}, - saveToken: function() { return token; } - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); - - return grantType.saveToken(token) - .then(function(data) { - data.should.equal(token); - }) - .catch(should.fail); - }); - - it('should support promises', function() { - var token = {}; - var model = { - getRefreshToken: function() {}, - revokeToken: function() {}, - saveToken: function() { return Promise.resolve(token); } - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.saveToken(token).should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var token = {}; - var model = { - getRefreshToken: function() {}, - revokeToken: function() {}, - saveToken: function() { return token; } - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.saveToken(token).should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function() { - var token = {}; - var model = { - getRefreshToken: function() {}, - revokeToken: function() {}, - saveToken: function(tokenToSave, client, user, callback) { callback(null, token); } - }; - var grantType = new RefreshTokenGrantType({ accessTokenLifetime: 123, model: model }); - - grantType.saveToken(token).should.be.an.instanceOf(Promise); - }); - }); -}); diff --git a/test/integration/handlers/authenticate-handler.spec.ts b/test/integration/handlers/authenticate-handler.spec.ts new file mode 100755 index 000000000..9200e9d01 --- /dev/null +++ b/test/integration/handlers/authenticate-handler.spec.ts @@ -0,0 +1,710 @@ +import * as should from 'should'; +import { + AccessDeniedError, + InsufficientScopeError, + InvalidArgumentError, + InvalidRequestError, + InvalidTokenError, + ServerError, + UnauthorizedRequestError, +} from '../../../lib/errors'; +import { AuthenticateHandler } from '../../../lib/handlers'; +import { Request } from '../../../lib/request'; +import { Response } from '../../../lib/response'; + +/** + * Test `AuthenticateHandler` integration. + */ + +describe('AuthenticateHandler integration', () => { + describe('constructor()', () => { + it('should throw an error if `options.model` is missing', () => { + try { + new AuthenticateHandler(); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `model`'); + } + }); + + it('should throw an error if the model does not implement `getAccessToken()`', () => { + try { + new AuthenticateHandler({ model: {} }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `getAccessToken()`', + ); + } + }); + + it('should throw an error if `scope` was given and `addAcceptedScopesHeader()` is missing', () => { + try { + new AuthenticateHandler({ + model: { getAccessToken() {} }, + scope: 'foobar', + }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `addAcceptedScopesHeader`'); + } + }); + + it('should throw an error if `scope` was given and `addAuthorizedScopesHeader()` is missing', () => { + try { + new AuthenticateHandler({ + addAcceptedScopesHeader: true, + model: { getAccessToken() {} }, + scope: 'foobar', + }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Missing parameter: `addAuthorizedScopesHeader`', + ); + } + }); + + it('should throw an error if `scope` was given and the model does not implement `verifyScope()`', () => { + try { + new AuthenticateHandler({ + addAcceptedScopesHeader: true, + addAuthorizedScopesHeader: true, + model: { getAccessToken() {} }, + scope: 'foobar', + }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `verifyScope()`', + ); + } + }); + + it('should set the `model`', () => { + const model = { getAccessToken() {} }; + const grantType = new AuthenticateHandler({ model }); + + grantType.model.should.equal(model); + }); + + it('should set the `scope`', () => { + const model = { + getAccessToken() {}, + verifyScope() {}, + }; + const grantType = new AuthenticateHandler({ + addAcceptedScopesHeader: true, + addAuthorizedScopesHeader: true, + model, + scope: 'foobar', + }); + + grantType.scope.should.equal('foobar'); + }); + }); + + describe('handle()', () => { + it('should throw an error if `request` is missing', async () => { + const handler = new AuthenticateHandler({ + model: { getAccessToken() {} }, + }); + + try { + await handler.handle(undefined, undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: `request` must be an instance of Request', + ); + } + }); + + it('should set the `WWW-Authenticate` header if an unauthorized request error is thrown', () => { + const model = { + getAccessToken() { + throw new UnauthorizedRequestError(undefined, undefined); + }, + }; + const handler = new AuthenticateHandler({ model }); + const request = new Request({ + body: {}, + headers: { Authorization: 'Bearer foo' }, + method: 'ANY', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(() => { + response + .get('WWW-Authenticate') + .should.equal('Bearer realm="Service"'); + }); + }); + + it('should throw the error if an oauth error is thrown', () => { + const model = { + getAccessToken() { + throw new AccessDeniedError('Cannot request this access token'); + }, + }; + const handler = new AuthenticateHandler({ model }); + const request = new Request({ + body: {}, + headers: { Authorization: 'Bearer foo' }, + method: 'ANY', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(AccessDeniedError); + e.message.should.equal('Cannot request this access token'); + }); + }); + + it('should throw a server error if a non-oauth error is thrown', () => { + const model = { + getAccessToken() { + throw new Error('Unhandled exception'); + }, + }; + const handler = new AuthenticateHandler({ model }); + const request = new Request({ + body: {}, + headers: { Authorization: 'Bearer foo' }, + method: 'ANY', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(ServerError); + e.message.should.equal('Unhandled exception'); + }); + }); + + it('should return an access token', () => { + const accessToken: any = { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + const model = { + getAccessToken() { + return accessToken; + }, + verifyScope() { + return true; + }, + }; + const handler = new AuthenticateHandler({ + addAcceptedScopesHeader: true, + addAuthorizedScopesHeader: true, + model, + scope: 'foo', + }); + const request = new Request({ + body: {}, + headers: { Authorization: 'Bearer foo' }, + method: 'ANY', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(data => { + data.should.equal(accessToken); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + + describe('getTokenFromRequest()', () => { + it('should throw an error if more than one authentication method is used', () => { + const handler = new AuthenticateHandler({ + model: { getAccessToken() {} }, + }); + const request = new Request({ + body: {}, + headers: { Authorization: 'Bearer foo' }, + method: 'ANY', + query: { access_token: 'foo' }, + }); + + try { + handler.getTokenFromRequest(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal( + 'Invalid request: only one authentication method is allowed', + ); + } + }); + + it('should throw an error if `accessToken` is missing', () => { + const handler = new AuthenticateHandler({ + model: { getAccessToken() {} }, + }); + const request = new Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + handler.getTokenFromRequest(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(UnauthorizedRequestError); + e.message.should.equal('Unauthorized request: no authentication given'); + } + }); + }); + + describe('getTokenFromRequestHeader()', () => { + it('should throw an error if the token is malformed', () => { + const handler = new AuthenticateHandler({ + model: { getAccessToken() {} }, + }); + const request = new Request({ + body: {}, + headers: { + Authorization: 'foobar', + }, + method: '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', + ); + } + }); + + it('should return the bearer token', () => { + const handler = new AuthenticateHandler({ + model: { getAccessToken() {} }, + }); + const request = new Request({ + body: {}, + headers: { + Authorization: 'Bearer foo', + }, + method: 'ANY', + query: {}, + }); + + const bearerToken = handler.getTokenFromRequestHeader(request); + + bearerToken.should.equal('foo'); + }); + }); + + describe('getTokenFromRequestQuery()', () => { + it('should throw an error if the query contains a token', () => { + const handler = new AuthenticateHandler({ + model: { getAccessToken() {} }, + }); + + try { + handler.getTokenFromRequestQuery(undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal( + 'Invalid request: do not send bearer tokens in query URLs', + ); + } + }); + + it('should return the bearer token if `allowBearerTokensInQueryString` is true', () => { + const handler = new AuthenticateHandler({ + allowBearerTokensInQueryString: true, + model: { getAccessToken() {} }, + }); + const req = { query: { access_token: 'foo' } }; + handler.getTokenFromRequestQuery(req as Request).should.equal('foo'); + }); + }); + + describe('getTokenFromRequestBody()', () => { + it('should throw an error if the method is `GET`', () => { + const handler = new AuthenticateHandler({ + model: { getAccessToken() {} }, + }); + const request = new Request({ + body: { access_token: 'foo' }, + headers: {}, + method: 'GET', + query: {}, + }); + + try { + handler.getTokenFromRequestBody(request); + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal( + 'Invalid request: token may not be passed in the body when using the GET verb', + ); + } + }); + + it('should throw an error if the media type is not `application/x-www-form-urlencoded`', () => { + const handler = new AuthenticateHandler({ + model: { getAccessToken() {} }, + }); + const request = new Request({ + body: { access_token: 'foo' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + handler.getTokenFromRequestBody(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal( + 'Invalid request: content must be application/x-www-form-urlencoded', + ); + } + }); + + it('should return the bearer token', () => { + const handler = new AuthenticateHandler({ + model: { getAccessToken() {} }, + }); + const request = new Request({ + body: { access_token: 'foo' }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'ANY', + query: {}, + }); + + handler.getTokenFromRequestBody(request).should.equal('foo'); + }); + }); + + describe('getAccessToken()', () => { + it('should throw an error if `accessToken` is missing', () => { + const model = { + getAccessToken() {}, + }; + const handler = new AuthenticateHandler({ model }); + + return handler + .getAccessToken('foo') + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(InvalidTokenError); + e.message.should.equal('Invalid token: access token is invalid'); + }); + }); + + it('should throw an error if `accessToken.user` is missing', () => { + const model = { + getAccessToken() { + return {}; + }, + }; + const handler = new AuthenticateHandler({ model }); + + return handler + .getAccessToken('foo') + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(ServerError); + e.message.should.equal( + 'Server error: `getAccessToken()` did not return a `user` object', + ); + }); + }); + + it('should return an access token', () => { + const accessToken: any = { user: {} }; + const model = { + getAccessToken() { + return accessToken; + }, + }; + const handler = new AuthenticateHandler({ model }); + + return handler + .getAccessToken('foo') + .then(data => { + data.should.equal(accessToken); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + + it('should support promises', () => { + const model = { + getAccessToken() { + return Promise.resolve({ user: {} }); + }, + }; + const handler = new AuthenticateHandler({ model }); + + handler.getAccessToken('foo').should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const model = { + getAccessToken() { + return { user: {} }; + }, + }; + const handler = new AuthenticateHandler({ model }); + + handler.getAccessToken('foo').should.be.an.instanceOf(Promise); + }); + + /* it('should support callbacks', () => { + const model = { + getAccessToken(token, callback) { + callback(null, { user: {} }); + }, + }; + const handler = new AuthenticateHandler({ model }); + + handler.getAccessToken('foo').should.be.an.instanceOf(Promise); + }); */ + }); + + describe('validateAccessToken()', () => { + it('should throw an error if `accessToken` is expired', () => { + const accessToken: any = { + accessTokenExpiresAt: new Date(new Date().getTime() / 2), + }; + const handler = new AuthenticateHandler({ + model: { getAccessToken() {} }, + }); + + try { + handler.validateAccessToken(accessToken); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidTokenError); + e.message.should.equal('Invalid token: access token has expired'); + } + }); + + it('should return an access token', () => { + const accessToken: any = { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + const handler = new AuthenticateHandler({ + model: { getAccessToken() {} }, + }); + + handler.validateAccessToken(accessToken).should.equal(accessToken); + }); + }); + + describe('verifyScope()', () => { + it('should throw an error if `scope` is insufficient', () => { + const model = { + getAccessToken() {}, + verifyScope() { + return false; + }, + }; + const handler = new AuthenticateHandler({ + addAcceptedScopesHeader: true, + addAuthorizedScopesHeader: true, + model, + scope: 'foo', + }); + + return handler + .verifyScope('foo' as any) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(InsufficientScopeError); + e.message.should.equal( + 'Insufficient scope: authorized scope is insufficient', + ); + }); + }); + + it('should support promises', () => { + const model = { + getAccessToken() {}, + verifyScope() { + return true; + }, + }; + const handler = new AuthenticateHandler({ + addAcceptedScopesHeader: true, + addAuthorizedScopesHeader: true, + model, + scope: 'foo', + }); + + handler.verifyScope('foo' as any).should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const model = { + getAccessToken() {}, + verifyScope() { + return true; + }, + }; + const handler = new AuthenticateHandler({ + addAcceptedScopesHeader: true, + addAuthorizedScopesHeader: true, + model, + scope: 'foo', + }); + + handler.verifyScope('foo' as any).should.be.an.instanceOf(Promise); + }); + + /* it('should support callbacks', () => { + const model = { + getAccessToken() {}, + verifyScope(token, scope, callback) { + callback(null, true); + }, + }; + const handler = new AuthenticateHandler({ + addAcceptedScopesHeader: true, + addAuthorizedScopesHeader: true, + model, + scope: 'foo', + }); + + handler.verifyScope('foo').should.be.an.instanceOf(Promise); + }); */ + }); + + describe('updateResponse()', () => { + it('should not set the `X-Accepted-OAuth-Scopes` header if `scope` is not specified', () => { + const model = { + getAccessToken() {}, + verifyScope() {}, + }; + const handler = new AuthenticateHandler({ + addAcceptedScopesHeader: true, + addAuthorizedScopesHeader: false, + model, + }); + const response = new Response({ body: {}, headers: {} }); + + handler.updateResponse(response, { scope: 'foo biz' } as any); + + response.headers.should.not.have.property('x-accepted-oauth-scopes'); + }); + + it('should set the `X-Accepted-OAuth-Scopes` header if `scope` is specified', () => { + const model = { + getAccessToken() {}, + verifyScope() {}, + }; + const handler = new AuthenticateHandler({ + addAcceptedScopesHeader: true, + addAuthorizedScopesHeader: false, + model, + scope: 'foo bar', + }); + const response = new Response({ body: {}, headers: {} }); + + handler.updateResponse(response, { scope: 'foo biz' } as any); + + response.get('X-Accepted-OAuth-Scopes').should.equal('foo bar'); + }); + + it('should not set the `X-Authorized-OAuth-Scopes` header if `scope` is not specified', () => { + const model = { + getAccessToken() {}, + verifyScope() {}, + }; + const handler = new AuthenticateHandler({ + addAcceptedScopesHeader: false, + addAuthorizedScopesHeader: true, + model, + }); + const response = new Response({ body: {}, headers: {} }); + + handler.updateResponse(response, { scope: 'foo biz' } as any); + + response.headers.should.not.have.property('x-oauth-scopes'); + }); + + it('should set the `X-Authorized-OAuth-Scopes` header', () => { + const model = { + getAccessToken() {}, + verifyScope() {}, + }; + const handler = new AuthenticateHandler({ + addAcceptedScopesHeader: false, + addAuthorizedScopesHeader: true, + model, + scope: 'foo bar', + }); + const response = new Response({ body: {}, headers: {} }); + + handler.updateResponse(response, { scope: 'foo biz' } as any); + + response.get('X-OAuth-Scopes').should.equal('foo biz'); + }); + }); +}); diff --git a/test/integration/handlers/authenticate-handler_test.js b/test/integration/handlers/authenticate-handler_test.js index 7852ea2eb..575c0eef4 100644 --- a/test/integration/handlers/authenticate-handler_test.js +++ b/test/integration/handlers/authenticate-handler_test.js @@ -132,6 +132,57 @@ describe('AuthenticateHandler integration', function() { }); }); + it('should set the `WWW-Authenticate` header if an InvalidRequestError is thrown', function() { + var model = { + getAccessToken: function() { + throw new InvalidRequestError(); + } + }; + var handler = new AuthenticateHandler({ model: model }); + var request = new Request({ body: {}, headers: { 'Authorization': 'Bearer foo' }, method: {}, query: {} }); + var response = new Response({ body: {}, headers: {} }); + + return handler.handle(request, response) + .then(should.fail) + .catch(function() { + response.get('WWW-Authenticate').should.equal('Bearer realm="Service",error="invalid_request"'); + }); + }); + + it('should set the `WWW-Authenticate` header if an InvalidTokenError is thrown', function() { + var model = { + getAccessToken: function() { + throw new InvalidTokenError(); + } + }; + var handler = new AuthenticateHandler({ model: model }); + var request = new Request({ body: {}, headers: { 'Authorization': 'Bearer foo' }, method: {}, query: {} }); + var response = new Response({ body: {}, headers: {} }); + + return handler.handle(request, response) + .then(should.fail) + .catch(function() { + response.get('WWW-Authenticate').should.equal('Bearer realm="Service",error="invalid_token"'); + }); + }); + + it('should set the `WWW-Authenticate` header if an InsufficientScopeError is thrown', function() { + var model = { + getAccessToken: function() { + throw new InsufficientScopeError(); + } + }; + var handler = new AuthenticateHandler({ model: model }); + var request = new Request({ body: {}, headers: { 'Authorization': 'Bearer foo' }, method: {}, query: {} }); + var response = new Response({ body: {}, headers: {} }); + + return handler.handle(request, response) + .then(should.fail) + .catch(function() { + response.get('WWW-Authenticate').should.equal('Bearer realm="Service",error="insufficient_scope"'); + }); + }); + it('should throw the error if an oauth error is thrown', function() { var model = { getAccessToken: function() { diff --git a/test/integration/handlers/authorize-handler.spec.ts b/test/integration/handlers/authorize-handler.spec.ts new file mode 100755 index 000000000..81eb04700 --- /dev/null +++ b/test/integration/handlers/authorize-handler.spec.ts @@ -0,0 +1,1622 @@ +import * as should from 'should'; +import * as url from 'url'; +import { + AccessDeniedError, + InvalidArgumentError, + InvalidClientError, + InvalidRequestError, + InvalidScopeError, + ServerError, + UnauthorizedClientError, +} from '../../../lib/errors'; +import { AuthenticateHandler, AuthorizeHandler } from '../../../lib/handlers'; +import { Request } from '../../../lib/request'; +import { Response } from '../../../lib/response'; +import { CodeResponseType } from '../../../lib/response-types'; + +/** + * Test `AuthorizeHandler` integration. + */ + +describe('AuthorizeHandler integration', () => { + describe('constructor()', () => { + // Move to Code Response Type + // it('should throw an error if `options.authorizationCodeLifetime` is missing', () => { + // try { + // new AuthorizeHandler({ model: {} }); + + // should.fail('should.fail', ''); + // } catch (e) { + // e.should.be.an.instanceOf(InvalidArgumentError); + // e.message.should.equal( + // 'Missing parameter: `authorizationCodeLifetime`', + // ); + // } + // }); + + it('should throw an error if `options.model` is missing', () => { + try { + new AuthorizeHandler({ authorizationCodeLifetime: 120 }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `model`'); + } + }); + + it('should throw an error if the model does not implement `getClient()`', () => { + try { + new AuthorizeHandler({ authorizationCodeLifetime: 120, model: {} }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `getClient()`', + ); + } + }); + + // Move to Code Response Type + // it('should throw an error if the model does not implement `saveAuthorizationCode()`', () => { + // try { + // new AuthorizeHandler({ + // authorizationCodeLifetime: 120, + // model: { getClient: () => {} }, + // }); + + // should.fail('should.fail', ''); + // } catch (e) { + // e.should.be.an.instanceOf(InvalidArgumentError); + // e.message.should.equal( + // 'Invalid argument: model does not implement `saveAuthorizationCode()`', + // ); + // } + // }); + + it('should throw an error if the model does not implement `getAccessToken()`', () => { + const model = { + getClient: () => {}, + saveAuthorizationCode: () => {}, + }; + + try { + new AuthorizeHandler({ authorizationCodeLifetime: 120, model }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `getAccessToken()`', + ); + } + }); + + // it('should set the `authorizationCodeLifetime`', () => { + // const model = { + // getAccessToken: () => {}, + // getClient: () => {}, + // saveAuthorizationCode: () => {}, + // }; + // const handler = new AuthorizeHandler({ + // authorizationCodeLifetime: 120, + // model, + // }); + + // handler.authorizationCodeLifetime.should.equal(120); + // }); + + it('should set the `authenticateHandler`', () => { + const model = { + getAccessToken: () => {}, + getClient: () => {}, + saveAuthorizationCode: () => {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + + handler.authenticateHandler.should.be.an.instanceOf(AuthenticateHandler); + }); + + it('should set the `model`', () => { + const model = { + getAccessToken: () => {}, + getClient: () => {}, + saveAuthorizationCode: () => {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + + handler.model.should.equal(model); + }); + }); + + describe('handle()', () => { + it('should throw an error if `request` is missing', async () => { + const model = { + getAccessToken: () => {}, + getClient: () => {}, + saveAuthorizationCode: () => {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + + try { + await handler.handle(undefined, undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: `request` must be an instance of Request', + ); + } + }); + + it('should throw an error if `response` is missing', async () => { + const model = { + getAccessToken: () => {}, + getClient: () => {}, + saveAuthorizationCode: () => {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + await handler.handle(request, undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: `response` must be an instance of Response', + ); + } + }); + + it('should throw an error if `allowed` is `false`', () => { + const model = { + getAccessToken: () => {}, + getClient: () => {}, + saveAuthorizationCode: () => {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: 'ANY', + query: { allowed: 'false' }, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(AccessDeniedError); + e.message.should.equal( + 'Access denied: user denied access to application', + ); + }); + }); + + it('should redirect to an error response if a non-oauth error is thrown', () => { + const model = { + getAccessToken: () => { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + getClient: () => { + return { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + }, + saveAuthorizationCode: () => { + throw new Error('Unhandled exception'); + }, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { + client_id: 12345, + response_type: 'code', + }, + headers: { + Authorization: 'Bearer foo', + }, + method: 'ANY', + query: { + state: 'foobar', + }, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(() => { + response + .get('location') + .should.equal( + 'http://example.com/cb?error=server_error&error_description=Unhandled%20exception&state=foobar', + ); + }); + }); + + it('should redirect to an error response if an oauth error is thrown', () => { + const model = { + getAccessToken: () => { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + getClient: () => { + return { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + }, + saveAuthorizationCode: () => { + throw new AccessDeniedError('Cannot request this auth code'); + }, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { + client_id: 12345, + response_type: 'code', + }, + headers: { + Authorization: 'Bearer foo', + }, + method: 'ANY', + query: { + state: 'foobar', + }, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(() => { + response.get('location').should.equal( + // tslint:disable-next-line:max-line-length + 'http://example.com/cb?error=access_denied&error_description=Cannot%20request%20this%20auth%20code&state=foobar', + ); + }); + }); + + it('should redirect to a successful response with `code` and `state` if successful', () => { + const client = { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + const model = { + getAccessToken: () => { + return { + client, + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + getClient: () => { + return client; + }, + saveAuthorizationCode: () => { + return { authorizationCode: 12345, client }; + }, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { + client_id: 12345, + response_type: 'code', + }, + headers: { + Authorization: 'Bearer foo', + }, + method: 'ANY', + query: { + state: 'foobar', + }, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => { + response + .get('location') + .should.equal('http://example.com/cb?code=12345&state=foobar'); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + + it('should redirect to an error response if `scope` is invalid', () => { + const model = { + getAccessToken: () => { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + getClient: () => { + return { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + }, + saveAuthorizationCode: () => { + return {}; + }, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { + client_id: 12345, + response_type: 'code', + }, + headers: { + Authorization: 'Bearer foo', + }, + method: 'ANY', + query: { + scope: [], + state: 'foobar', + }, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(() => { + response + .get('location') + .should.equal( + 'http://example.com/cb?error=invalid_scope&error_description=Invalid%20parameter%3A%20%60scope%60', + ); + }); + }); + + it('should redirect to an error response if `state` is missing', () => { + const model = { + getAccessToken: () => { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + getClient: () => { + return { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + }, + saveAuthorizationCode: () => { + throw new AccessDeniedError('Cannot request this auth code'); + }, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { + client_id: 12345, + response_type: 'code', + }, + headers: { + Authorization: 'Bearer foo', + }, + method: 'ANY', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(() => { + response + .get('location') + .should.equal( + 'http://example.com/cb?error=invalid_request&error_description=Missing%20parameter%3A%20%60state%60', + ); + }); + }); + + it('should redirect to an error response if `response_type` is invalid', () => { + const model = { + getAccessToken: () => { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + getClient: () => { + return { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + }, + saveAuthorizationCode: () => { + return { authorizationCode: 12345, client: {} }; + }, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { + client_id: 12345, + response_type: 'test', + }, + headers: { + Authorization: 'Bearer foo', + }, + method: 'ANY', + query: { + state: 'foobar', + }, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(() => { + response.get('location').should.equal( + // tslint:disable-next-line:max-line-length + 'http://example.com/cb?error=unsupported_response_type&error_description=Unsupported%20response%20type%3A%20%60response_type%60%20is%20not%20supported&state=foobar', + ); + }); + }); + + it('should fail on invalid `response_type` before calling model.saveAuthorizationCode()', () => { + const model = { + getAccessToken: () => { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + getClient: () => { + return { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + }, + saveAuthorizationCode: () => { + throw new Error('must not be reached'); + }, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { + client_id: 12345, + response_type: 'test', + }, + headers: { + Authorization: 'Bearer foo', + }, + method: 'ANY', + query: { + state: 'foobar', + }, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(() => { + response.get('location').should.equal( + // tslint:disable-next-line:max-line-length + 'http://example.com/cb?error=unsupported_response_type&error_description=Unsupported%20response%20type%3A%20%60response_type%60%20is%20not%20supported&state=foobar', + ); + }); + }); + + it('should return the `code` if successful', () => { + const client = { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + const model = { + getAccessToken: () => { + return { + client, + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + getClient() { + return client; + }, + saveAuthorizationCode() { + return { authorizationCode: 12345, client }; + }, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { + client_id: 12345, + response_type: 'code', + }, + headers: { + Authorization: 'Bearer foo', + }, + method: 'ANY', + query: { + state: 'foobar', + }, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(data => { + data.should.eql({ + authorizationCode: 12345, + client, + }); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + + // describe('generateAuthorizationCode()', () => { + // it('should return an auth code', async () => { + // const model = { + // getAccessToken() {}, + // getClient() {}, + // saveAuthorizationCode() {}, + // }; + // const handler = new AuthorizeHandler({ + // authorizationCodeLifetime: 120, + // model, + // }); + // try { + // const data: any = await handler.generateAuthorizationCode( + // undefined, + // undefined, + // undefined, + // ); + // data.should.be.a.sha1(); + // } catch (error) { + // should.fail('should.fail', ''); + // } + // }); + + // it('should support promises', async () => { + // const model = { + // generateAuthorizationCode() { + // return Promise.resolve({}); + // }, + // getAccessToken() {}, + // getClient() {}, + // saveAuthorizationCode() {}, + // }; + // const handler = new AuthorizeHandler({ + // authorizationCodeLifetime: 120, + // model, + // }); + // try { + // await handler + // .generateAuthorizationCode(undefined, undefined, undefined) + // .should.be.an.instanceOf(Promise); + // } catch (error) { + // should.fail('should.fail', ''); + // } + // }); + + // /* it('should support non-promises', () => { + // const model = { + // generateAuthorizationCode() { + // return {}; + // }, + // getAccessToken() {}, + // getClient() {}, + // saveAuthorizationCode() {}, + // }; + // const handler = new AuthorizeHandler({ + // authorizationCodeLifetime: 120, + // model, + // }); + + // handler + // .generateAuthorizationCode(undefined, undefined, undefined) + // .should.be.an.instanceOf(Promise); + // }); */ + // }); + + // describe('getAuthorizationCodeLifetime()', () => { + // it('should return a date', () => { + // const model = { + // getAccessToken() {}, + // getClient() {}, + // saveAuthorizationCode() {}, + // }; + // const handler = new AuthorizeHandler({ + // authorizationCodeLifetime: 120, + // model, + // }); + + // handler.getAuthorizationCodeLifetime().should.be.an.instanceOf(Date); + // }); + // }); + + describe('getClient()', () => { + it('should throw an error if `client_id` is missing', async () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { response_type: 'code' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + await handler.getClient(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Missing parameter: `client_id`'); + } + }); + + it('should throw an error if `client_id` is invalid', async () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { client_id: 'øå€£‰', response_type: 'code' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + await handler.getClient(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Invalid parameter: `client_id`'); + } + }); + + it('should throw an error if `client.redirectUri` is invalid', async () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { + client_id: 12345, + response_type: 'code', + redirect_uri: 'foobar', + }, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + await handler.getClient(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal( + 'Invalid request: `redirect_uri` is not a valid URI', + ); + } + }); + + it('should throw an error if `client` is missing', () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { client_id: 12345, response_type: 'code' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return handler + .getClient(request) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(InvalidClientError); + e.message.should.equal( + 'Invalid client: client credentials are invalid', + ); + }); + }); + + it('should throw an error if `client.grants` is missing', () => { + const model = { + getAccessToken() {}, + getClient() { + return {}; + }, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { client_id: 12345, response_type: 'code' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return handler + .getClient(request) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(InvalidClientError); + e.message.should.equal('Invalid client: missing client `grants`'); + }); + }); + + it('should throw an error if `client` is unauthorized', () => { + const model = { + getAccessToken() {}, + getClient() { + return { grants: [] }; + }, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { client_id: 12345, response_type: 'code' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return handler + .getClient(request) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(UnauthorizedClientError); + e.message.should.equal( + 'Unauthorized client: `grant_type` is invalid', + ); + }); + }); + + it('should throw an error if `client.redirectUri` is missing', () => { + const model = { + getAccessToken() {}, + getClient() { + return { grants: ['authorization_code'] }; + }, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { client_id: 12345, response_type: 'code' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return handler + .getClient(request) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(InvalidClientError); + e.message.should.equal( + 'Invalid client: missing client `redirectUri`', + ); + }); + }); + + it('should throw an error if `client.redirectUri` is not equal to `redirectUri`', () => { + const model = { + getAccessToken() {}, + getClient() { + return { + grants: ['authorization_code'], + redirectUris: ['https://example.com'], + }; + }, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { + client_id: 12345, + response_type: 'code', + redirect_uri: 'https://foobar.com', + }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return handler + .getClient(request) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(InvalidClientError); + e.message.should.equal( + 'Invalid client: `redirect_uri` does not match client value', + ); + }); + }); + + it('should support promises', async () => { + const model = { + getAccessToken() {}, + async getClient() { + return { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + }, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { client_id: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + handler.getClient(request).should.be.an.instanceOf(Promise); + } catch (error) { + should.fail('should.fail', ''); + } + }); + + /* it('should support non-promises', async () => { + const model = { + getAccessToken() {}, + getClient() { + return { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + }, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { client_id: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + + await handler.getClient(request).should.be.an.instanceOf(Promise); + }); */ + + /* it('should support callbacks', () => { + const model = { + getAccessToken() {}, + getClient(clientId, clientSecret, callback) { + should.equal(clientSecret, null); + callback(null, { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }); + }, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { client_id: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + + handler.getClient(request).should.be.an.instanceOf(Promise); + }); */ + + describe('with `client_id` in the request query', () => { + it('should return a client', () => { + const client = { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + const model = { + getAccessToken() {}, + getClient() { + return client; + }, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { response_type: 'code' }, + headers: {}, + method: 'ANY', + query: { client_id: 12345 }, + }); + + return handler + .getClient(request) + .then(data => { + data.should.equal(client); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + }); + + describe('getScope()', () => { + it('should throw an error if `scope` is invalid', () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { scope: 'øå€£‰' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + handler.getScope(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidScopeError); + e.message.should.equal('Invalid parameter: `scope`'); + } + }); + + describe('with `scope` in the request body', () => { + it('should return the scope', () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { scope: 'foo' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + handler.getScope(request).should.equal('foo'); + }); + }); + + describe('with `scope` in the request query', () => { + it('should return the scope', () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: 'ANY', + query: { scope: 'foo' }, + }); + + handler.getScope(request).should.equal('foo'); + }); + }); + }); + + describe('getState()', () => { + it('should throw an error if `allowEmptyState` is false and `state` is missing', () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + allowEmptyState: false, + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + handler.getState(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Missing parameter: `state`'); + } + }); + + it('should throw an error if `state` is invalid', () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: 'ANY', + query: { state: 'øå€£‰' }, + }); + + try { + handler.getState(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Invalid parameter: `state`'); + } + }); + + describe('with `state` in the request body', () => { + it('should return the state', () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { state: 'foobar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + handler.getState(request).should.equal('foobar'); + }); + }); + + describe('with `state` in the request query', () => { + it('should return the state', () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: 'ANY', + query: { state: 'foobar' }, + }); + + handler.getState(request).should.equal('foobar'); + }); + }); + }); + + describe('getUser()', () => { + it('should throw an error if `user` is missing', () => { + const authenticateHandler = { handle() {} }; + const model = { + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authenticateHandler, + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + const response = new Response(); + + return handler + .getUser(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(ServerError); + e.message.should.equal( + 'Server error: `handle()` did not return a `user` object', + ); + }); + }); + + it('should return a user', () => { + const user = {}; + const model = { + getAccessToken() { + return { + user, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: { Authorization: 'Bearer foo' }, + method: 'ANY', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .getUser(request, response) + .then(data => { + data.should.equal(user); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + + // describe('saveAuthorizationCode()', () => { + // it('should return an auth code', () => { + // const authorizationCode = {}; + // const model = { + // getAccessToken() {}, + // getClient() {}, + // saveAuthorizationCode() { + // return authorizationCode; + // }, + // }; + // const handler = new AuthorizeHandler({ + // authorizationCodeLifetime: 120, + // model, + // }); + + // return handler + // .saveAuthorizationCode( + // 'foo', + // 'bar' as any, + // 'biz', + // 'baz' as any, + // undefined, + // undefined, + // ) + // .then(data => { + // data.should.equal(authorizationCode); + // }) + // .catch(() => should.fail('should.fail', '')); + // }); + + // it('should support promises when calling `model.saveAuthorizationCode()`', () => { + // const model = { + // getAccessToken() {}, + // getClient() {}, + // saveAuthorizationCode() { + // return Promise.resolve({}); + // }, + // }; + // const handler = new AuthorizeHandler({ + // authorizationCodeLifetime: 120, + // model, + // }); + + // handler + // .saveAuthorizationCode( + // 'foo', + // 'bar' as any, + // 'biz', + // 'baz' as any, + // undefined, + // undefined, + // ) + // .should.be.an.instanceOf(Promise); + // }); + + // /* it('should support non-promises when calling `model.saveAuthorizationCode()`', () => { + // const model = { + // getAccessToken() {}, + // getClient() {}, + // saveAuthorizationCode() { + // return {}; + // }, + // }; + // const handler = new AuthorizeHandler({ + // authorizationCodeLifetime: 120, + // model, + // }); + + // handler + // .saveAuthorizationCode('foo', 'bar', 'biz', 'baz', undefined, undefined) + // .should.be.an.instanceOf(Promise); + // }); */ + + // /* it('should support callbacks when calling `model.saveAuthorizationCode()`', () => { + // const model = { + // getAccessToken() {}, + // getClient() {}, + // saveAuthorizationCode(code, client, user, callback) { + // return callback(null, true); + // }, + // }; + // const handler = new AuthorizeHandler({ + // authorizationCodeLifetime: 120, + // model, + // }); + + // handler + // .saveAuthorizationCode('foo', 'bar', 'biz', 'baz') + // .should.be.an.instanceOf(Promise); + // }); */ + // }); + + // describe('getResponseType()', () => { + // it('should throw an error if `response_type` is missing', () => { + // const model = { + // getAccessToken() {}, + // getClient() {}, + // saveAuthorizationCode() {}, + // }; + // const handler = new AuthorizeHandler({ + // authorizationCodeLifetime: 120, + // model, + // }); + // const request = new Request({ + // body: {}, + // headers: {}, + // method: 'ANY', + // query: {}, + // }); + + // try { + // handler.getResponseType(request); + + // should.fail('should.fail', ''); + // } catch (e) { + // e.should.be.an.instanceOf(InvalidRequestError); + // e.message.should.equal('Missing parameter: `response_type`'); + // } + // }); + + // it('should throw an error if `response_type` is not `code`', () => { + // const model = { + // getAccessToken() {}, + // getClient() {}, + // saveAuthorizationCode() {}, + // }; + // const handler = new AuthorizeHandler({ + // authorizationCodeLifetime: 120, + // model, + // }); + // const request = new Request({ + // body: { response_type: 'foobar' }, + // headers: {}, + // method: 'ANY', + // query: {}, + // }); + + // try { + // handler.getResponseType(request); + + // should.fail('should.fail', ''); + // } catch (e) { + // e.should.be.an.instanceOf(UnsupportedResponseTypeError); + // e.message.should.equal( + // 'Unsupported response type: `response_type` is not supported', + // ); + // } + // }); + + // describe('with `response_type` in the request body', () => { + // it('should return a response type', () => { + // const model = { + // getAccessToken() {}, + // getClient() {}, + // saveAuthorizationCode() {}, + // }; + // const handler = new AuthorizeHandler({ + // authorizationCodeLifetime: 120, + // model, + // }); + // const request = new Request({ + // body: { response_type: 'code' }, + // headers: {}, + // method: 'ANY', + // query: {}, + // }); + // const ResponseType = handler.getResponseType(request); + + // ResponseType.should.equal(CodeResponseType); + // }); + // }); + + // describe('with `response_type` in the request query', () => { + // it('should return a response type', () => { + // const model = { + // getAccessToken() {}, + // getClient() {}, + // saveAuthorizationCode() {}, + // }; + // const handler = new AuthorizeHandler({ + // authorizationCodeLifetime: 120, + // model, + // }); + // const request = new Request({ + // body: {}, + // headers: {}, + // method: 'ANY', + // query: { response_type: 'code' }, + // }); + // const ResponseType = handler.getResponseType(request); + + // ResponseType.should.equal(CodeResponseType); + // }); + // }); + // }); + + describe('buildSuccessRedirectUri()', () => { + it('should return a redirect uri', () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const responseType = new CodeResponseType({ + authorizationCodeLifetime: 360, + model: { saveAuthorizationCode: () => {} }, + }); + responseType.code = 12345; + const redirectUri = handler.buildSuccessRedirectUri( + 'http://example.com/cb', + responseType, + ); + + url.format(redirectUri).should.equal('http://example.com/cb?code=12345'); + }); + }); + + describe('buildErrorRedirectUri()', () => { + it('should set `error_description` if available', () => { + const error = new InvalidClientError('foo bar'); + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const responseType = new CodeResponseType({ + authorizationCodeLifetime: 360, + model: { saveAuthorizationCode: () => {} }, + }); + const redirectUri = handler.buildErrorRedirectUri( + 'http://example.com/cb', + responseType, + error, + ); + + url + .format(redirectUri) + .should.equal( + 'http://example.com/cb?error=invalid_client&error_description=foo%20bar', + ); + }); + + it('should return a redirect uri', () => { + const error = new InvalidClientError(); + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const responseType = new CodeResponseType({ + authorizationCodeLifetime: 360, + model: { saveAuthorizationCode: () => {} }, + }); + const redirectUri = handler.buildErrorRedirectUri( + 'http://example.com/cb', + responseType, + error, + ); + + url + .format(redirectUri) + .should.equal( + 'http://example.com/cb?error=invalid_client&error_description=Bad%20Request', + ); + }); + }); + + describe('updateResponse()', () => { + it('should set the `location` header', () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const responseType = new CodeResponseType({ + authorizationCodeLifetime: 360, + model: { saveAuthorizationCode: () => {} }, + }); + const response = new Response({ body: {}, headers: {} }); + const uri = url.parse('http://example.com/cb', true); + + handler.updateResponse(response, uri, responseType, 'foobar'); + + response + .get('location') + .should.equal('http://example.com/cb?state=foobar'); + }); + }); +}); diff --git a/test/integration/handlers/authorize-handler_test.js b/test/integration/handlers/authorize-handler_test.js index f895f82e3..e2eb70ba2 100644 --- a/test/integration/handlers/authorize-handler_test.js +++ b/test/integration/handlers/authorize-handler_test.js @@ -159,21 +159,40 @@ describe('AuthorizeHandler integration', function() { } }); - it('should throw an error if `allowed` is `false`', function() { + it('should redirect to an error response if user denied access', function() { var model = { - getAccessToken: function() {}, - getClient: function() {}, + getAccessToken: function() { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000) + }; + }, + getClient: function() { + return { grants: ['authorization_code'], redirectUris: ['http://example.com/cb'] }; + }, saveAuthorizationCode: function() {} }; var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: { allowed: 'false' } }); + var request = new Request({ + body: { + client_id: 12345, + response_type: 'code' + }, + method: {}, + headers: { + 'Authorization': 'Bearer foo' + }, + query: { + state: 'foobar', + allowed: 'false' + } + }); var response = new Response({ body: {}, headers: {} }); return handler.handle(request, response) .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(AccessDeniedError); - e.message.should.equal('Access denied: user denied access to application'); + .catch(function() { + response.get('location').should.equal('http://example.com/cb?error=access_denied&error_description=Access%20denied%3A%20user%20denied%20access%20to%20application&state=foobar'); }); }); diff --git a/test/integration/handlers/revoke-handler.spec.ts b/test/integration/handlers/revoke-handler.spec.ts new file mode 100644 index 000000000..362802b9b --- /dev/null +++ b/test/integration/handlers/revoke-handler.spec.ts @@ -0,0 +1,1114 @@ +import * as should from 'should'; +import * as util from 'util'; +import { + AccessDeniedError, + InvalidArgumentError, + InvalidClientError, + InvalidRequestError, + InvalidTokenError, + ServerError, +} from '../../../lib/errors'; +import { RevokeHandler } from '../../../lib/handlers'; +import { Request } from '../../../lib/request'; +import { Response } from '../../../lib/response'; + +/** + * Test `RevokeHandler` integration. + */ + +describe('RevokeHandler integration', () => { + describe('constructor()', () => { + it('should throw an error if `options.model` is missing', () => { + try { + new RevokeHandler({}); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `model`'); + } + }); + + it('should throw an error if the model does not implement `getClient()`', () => { + try { + new RevokeHandler({ model: {} }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `getClient()`', + ); + } + }); + + it('should set the `model`', () => { + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + + handler.model.should.equal(model); + }); + }); + + describe('handle()', () => { + it('should throw an error if `request` is missing', async () => { + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + + try { + await handler.handle(); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: `request` must be an instance of Request', + ); + } + }); + + it('should throw an error if `response` is missing', async () => { + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + await handler.handle(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: `response` must be an instance of Response', + ); + } + }); + + it('should throw an error if the method is not `POST`', () => { + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: {}, + headers: {}, + method: 'GET', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Invalid request: method must be POST'); + }); + }); + + it('should throw an error if the media type is not `application/x-www-form-urlencoded`', () => { + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: {}, + headers: {}, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal( + 'Invalid request: content must be application/x-www-form-urlencoded', + ); + }); + }); + + it('should throw the error if an oauth error is thrown', () => { + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { token: 'hash' }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(InvalidClientError); + e.message.should.equal( + 'Invalid client: cannot retrieve client credentials', + ); + }); + }); + + it('should throw the error if an oauth error is thrown', () => { + const model = { + getClient() { + return { grants: ['password'] }; + }, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Missing parameter: `token`'); + }); + }); + + it('should throw a server error if a non-oauth error is thrown', () => { + const model = { + getClient() { + throw new Error('Unhandled exception'); + }, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { + client_id: 12345, + client_secret: 'secret', + token: 'hash', + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(ServerError); + e.message.should.equal('Unhandled exception'); + e.inner.should.be.an.instanceOf(Error); + }); + }); + + it('should update the response if an error is thrown', () => { + const model = { + getClient() { + throw new Error('Unhandled exception'); + }, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { + client_id: 12345, + client_secret: 'secret', + grant_type: 'password', + password: 'bar', + username: 'foo', + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(should.fail) + .catch(() => { + response.body.should.eql({ + error: 'server_error', + error_description: 'Unhandled exception', + }); + response.status.should.equal(500); + }); + }); + + it('should not update the response if an invalid token error is thrown', () => { + const token = { + refreshToken: 'hash', + client: {}, + user: {}, + refreshTokenExpiresAt: new Date('2015-01-01'), + }; + const client = { grants: ['password'] }; + const model = { + getClient() { + return client; + }, + revokeToken() { + return token; + }, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { + client_id: 12345, + client_secret: 'secret', + token: 'hash', + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(should.fail) + .catch(e => { + e[0].should.be.an.instanceOf(InvalidTokenError); + e[1].should.be.an.instanceOf(InvalidTokenError); + response.body.should.eql({}); + response.status.should.equal(200); + }); + }); + + it('should return an empty object if successful', () => { + const token = { + refreshToken: 'hash', + client: {}, + user: {}, + refreshTokenExpiresAt: new Date(Date.now() * 2), + }; + const client = { grants: ['password'] }; + const model = { + getClient() { + return client; + }, + revokeToken() { + return token; + }, + getRefreshToken() { + return token; + }, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { + client_id: 12345, + client_secret: 'secret', + token: 'hash', + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(data => { + should.exist(data); + }) + .catch(should.fail); + }); + }); + + describe('getClient()', () => { + it('should throw an error if `clientId` is invalid', async () => { + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { client_id: 'øå€£‰', client_secret: 'foo' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + await handler.getClient(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Invalid parameter: `client_id`'); + } + }); + + it('should throw an error if `clientId` is invalid', async () => { + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { client_id: 'foo', client_secret: 'øå€£‰' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + await handler.getClient(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Invalid parameter: `client_secret`'); + } + }); + + it('should throw an error if `client` is missing', () => { + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return handler + .getClient(request) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(InvalidClientError); + e.message.should.equal('Invalid client: client is invalid'); + }); + }); + + it('should throw an error if `client.grants` is missing', () => { + const model = { + getClient() { + return {}; + }, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return handler + .getClient(request) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(ServerError); + e.message.should.equal('Server error: missing client `grants`'); + }); + }); + + it('should throw a 401 error if the client is invalid and the request contains an authorization header', () => { + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: {}, + headers: { + authorization: util.format( + 'Basic %s', + Buffer.from('foo:bar').toString('base64'), + ), + }, + method: 'ANY', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .getClient(request, response) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(InvalidClientError); + e.code.should.equal(401); + e.message.should.equal('Invalid client: client is invalid'); + + response + .get('WWW-Authenticate') + .should.equal('Basic realm="Service"'); + }); + }); + + it('should return a client', () => { + const client = { id: 12345, grants: [] }; + const model = { + getClient() { + return client; + }, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return handler + .getClient(request) + .then(data => { + data.should.equal(client); + }) + .catch(should.fail); + }); + + it('should support promises', () => { + const model = { + getClient() { + return Promise.resolve({ grants: [] }); + }, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + handler.getClient(request).should.be.an.instanceOf(Promise); + }); + + // it('should support callbacks', () => { + // const model = { + // getClient(clientId, clientSecret, callback) { + // callback(null, { grants: [] }); + // }, + // revokeToken() {}, + // getRefreshToken() {}, + // getAccessToken() {}, + // }; + // const handler:any = new RevokeHandler({ model }); + // const request = new Request({ + // body: { client_id: 12345, client_secret: 'secret' }, + // headers: {}, + // method: 'ANY', + // query: {}, + // }); + + // handler.getClient(request).should.be.an.instanceOf(Promise); + // }); + + it('should support non-promises', () => { + const model = { + getClient() { + return { grants: [] }; + }, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + handler.getClient(request).should.be.an.instanceOf(Promise); + }); + }); + + describe('getClientCredentials()', () => { + it('should throw an error if `client_id` is missing', () => { + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { client_secret: 'foo' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + handler.getClientCredentials(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidClientError); + e.message.should.equal( + 'Invalid client: cannot retrieve client credentials', + ); + } + }); + + it('should throw an error if `client_secret` is missing', () => { + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { client_id: 'foo' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + handler.getClientCredentials(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidClientError); + e.message.should.equal( + 'Invalid client: cannot retrieve client credentials', + ); + } + }); + + describe('with `client_id` and `client_secret` in the request header as basic auth', () => { + it('should return a client', () => { + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: {}, + headers: { + authorization: util.format( + 'Basic %s', + Buffer.from('foo:bar').toString('base64'), + ), + }, + method: 'ANY', + query: {}, + }); + const credentials = handler.getClientCredentials(request); + + credentials.should.eql({ clientId: 'foo', clientSecret: 'bar' }); + }); + }); + + describe('with `client_id` and `client_secret` in the request body', () => { + it('should return a client', () => { + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { client_id: 'foo', client_secret: 'bar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + const credentials = handler.getClientCredentials(request); + + credentials.should.eql({ clientId: 'foo', clientSecret: 'bar' }); + }); + }); + }); + + describe('handleRevokeToken()', () => { + it('should throw an error if `token` is missing', () => { + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + + return handler + .handleRevokeToken(request) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Missing parameter: `token`'); + }); + }); + + it('should return a token', () => { + const client = { id: 12345, grants: ['password'] }; + const token = { + accessToken: 'hash', + client: { id: 12345 }, + accessTokenExpiresAt: new Date(Date.now() * 2), + user: {}, + }; + const model = { + getClient() {}, + revokeToken() { + return token; + }, + getRefreshToken() {}, + getAccessToken() { + return token; + }, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { token: 'hash' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return handler + .handleRevokeToken(request, client) + .then(data => { + should.exist(data); + }) + .catch(should.fail); + }); + + it('should return a token', () => { + const client = { id: 12345, grants: ['password'] }; + const token = { + refreshToken: 'hash', + client: { id: 12345 }, + refreshTokenExpiresAt: new Date(Date.now() * 2), + user: {}, + }; + const model = { + getClient() {}, + revokeToken() { + return token; + }, + getRefreshToken() { + return token; + }, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { token: 'hash' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return handler + .handleRevokeToken(request, client) + .then(data => { + should.exist(data); + }) + .catch(should.fail); + }); + }); + + describe('getRefreshToken()', () => { + it('should throw an error if the `refreshToken` is invalid', () => { + const client = {}; + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + + return handler + .getRefreshToken('hash', client) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(InvalidTokenError); + e.message.should.equal('Invalid token: refresh token is invalid'); + }); + }); + + it('should throw an error if the `client_id` does not match', () => { + const client = { id: 'foo' }; + const token = { + refreshToken: 'hash', + client: { id: 'baz' }, + user: {}, + refreshTokenExpiresAt: new Date(Date.now() * 2), + }; + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() { + return token; + }, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + + return handler + .getRefreshToken('hash', client) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(InvalidClientError); + e.message.should.equal('Invalid client: client is invalid'); + }); + }); + + it('should return a token', () => { + const client = { id: 'foo' }; + const token = { + refreshToken: 'hash', + client: { id: 'foo' }, + user: {}, + refreshTokenExpiresAt: new Date(Date.now() * 2), + }; + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() { + return token; + }, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + + return handler + .getRefreshToken('hash', client) + .then(Token => { + should.exist(Token); + }) + .catch(should.fail); + }); + + // it('should support callbacks', () => { + // const client = { id: 'foo' }; + // const token = { + // refreshToken: 'hash', + // client: { id: 'foo' }, + // user: {}, + // refreshTokenExpiresAt: new Date(Date.now() * 2), + // }; + // const model = { + // getClient() {}, + // revokeToken() {}, + // getRefreshToken(refreshToken, callback) { + // callback(null, token); + // }, + // getAccessToken() {}, + // }; + // const handler:any = new RevokeHandler({ model }); + + // return handler + // .getRefreshToken('hash', client) + // .then(token => { + // should.exist(token); + // }) + // .catch(should.fail); + // }); + }); + + describe('getAccessToken()', () => { + it('should throw an error if the `accessToken` is invalid', () => { + const client = {}; + const model = { + getClient() {}, + revokeToken() {}, + getAccessToken() {}, + getRefreshToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + + return handler + .getAccessToken('hash', client) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(InvalidTokenError); + e.message.should.equal('Invalid token: access token is invalid'); + }); + }); + + it('should throw an error if the `client_id` does not match', () => { + const client = { id: 'foo' }; + const token = { + accessToken: 'hash', + client: { id: 'baz' }, + user: {}, + accessTokenExpiresAt: new Date(Date.now() * 2), + }; + const model = { + getClient() {}, + revokeToken() {}, + getAccessToken() { + return token; + }, + getRefreshToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + + return handler + .getAccessToken('hash', client) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(InvalidClientError); + e.message.should.equal('Invalid client: client is invalid'); + }); + }); + + it('should return a token', () => { + const client = { id: 'foo' }; + const token = { + accessToken: 'hash', + client: { id: 'foo' }, + user: {}, + accessTokenExpiresAt: new Date(Date.now() * 2), + }; + const model = { + getClient() {}, + revokeToken() {}, + getAccessToken() { + return token; + }, + getRefreshToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + + return handler + .getAccessToken('hash', client) + .then(Token => { + should.exist(Token); + }) + .catch(should.fail); + }); + + // it('should support callbacks', () => { + // const client = { id: 'foo' }; + // const token = { + // accessToken: 'hash', + // client: { id: 'foo' }, + // user: {}, + // accessTokenExpiresAt: new Date(Date.now() * 2), + // }; + // const model = { + // getClient() {}, + // revokeToken() {}, + // getAccessToken(accessToken, callback) { + // callback(null, token); + // }, + // getRefreshToken() {}, + // }; + // const handler:any = new RevokeHandler({ model }); + + // return handler + // .getAccessToken('hash', client) + // .then(token => { + // should.exist(token); + // }) + // .catch(should.fail); + // }); + }); + + describe('revokeToken()', () => { + it('should throw an error if the `refreshToken` is invalid', () => { + const token = 'hash'; + const client = {}; + const model = { + getClient() {}, + revokeToken() { + return false; + }, + getRefreshToken() { + return { client: {}, user: {} }; + }, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + + return handler + .revokeToken(token, client) + .then(should.fail) + .catch(e => { + e.should.be.an.instanceOf(InvalidTokenError); + e.message.should.equal('Invalid token: token is invalid'); + }); + }); + + // it('should support callbacks', () => { + // const token = {}; + // const client = {}; + // const model = { + // getClient() {}, + // revokeToken(tokenObject, callback) { + // callback(null, null); + // }, + // getRefreshToken(refreshToken, callback) { + // callback(null, { client: {}, user: {} }); + // }, + // getAccessToken() {}, + // }; + // const handler:any = new RevokeHandler({ model }); + + // return handler + // .revokeToken(token, client) + // .then(should.fail) + // .catch(e => { + // e.should.be.an.instanceOf(InvalidTokenError); + // e.message.should.equal('Invalid token: token is invalid'); + // }); + // }); + }); + + describe('getTokenFromRequest()', () => { + it('should throw an error if `accessToken` is missing', () => { + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + handler.getTokenFromRequest(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Missing parameter: `token`'); + } + }); + }); + + describe('updateErrorResponse()', () => { + it('should set the `body`', () => { + const error = new AccessDeniedError('Cannot request a revoke'); + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const response = new Response({ body: {}, headers: {} }); + + handler.updateErrorResponse(response, error); + + response.body.error.should.equal('access_denied'); + response.body.error_description.should.equal('Cannot request a revoke'); + }); + + it('should set the `status`', () => { + const error = new AccessDeniedError('Cannot request a revoke'); + const model = { + getClient() {}, + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const response = new Response({ body: {}, headers: {} }); + + handler.updateErrorResponse(response, error); + + response.status.should.equal(400); + }); + }); +}); diff --git a/test/integration/handlers/token-handler.spec.ts b/test/integration/handlers/token-handler.spec.ts new file mode 100755 index 000000000..25e861479 --- /dev/null +++ b/test/integration/handlers/token-handler.spec.ts @@ -0,0 +1,1670 @@ +import * as should from 'should'; +import * as util from 'util'; +import { + AccessDeniedError, + InvalidArgumentError, + InvalidClientError, + InvalidRequestError, + ServerError, + UnauthorizedClientError, + UnsupportedGrantTypeError, +} from '../../../lib/errors'; +import { PasswordGrantType } from '../../../lib/grant-types'; +import { TokenHandler } from '../../../lib/handlers'; +import { Request } from '../../../lib/request'; +import { Response } from '../../../lib/response'; +import { BearerTokenType } from '../../../lib/token-types'; + +/** + * Test `TokenHandler` integration. + */ + +describe('TokenHandler integration', () => { + describe('constructor()', () => { + it('should throw an error if `options.accessTokenLifetime` is missing', () => { + try { + new TokenHandler(); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `accessTokenLifetime`'); + } + }); + + it('should throw an error if `options.model` is missing', () => { + try { + new TokenHandler({ accessTokenLifetime: 120 }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `model`'); + } + }); + + it('should throw an error if `options.refreshTokenLifetime` is missing', () => { + try { + new TokenHandler({ accessTokenLifetime: 120, model: {} }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `refreshTokenLifetime`'); + } + }); + + it('should throw an error if the model does not implement `getClient()`', () => { + try { + new TokenHandler({ + accessTokenLifetime: 120, + model: {}, + refreshTokenLifetime: 120, + }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `getClient()`', + ); + } + }); + + it('should set the `accessTokenLifetime`', () => { + const accessTokenLifetime = {}; + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime, + model, + refreshTokenLifetime: 120, + }); + + handler.accessTokenLifetime.should.equal(accessTokenLifetime); + }); + + it('should set the `alwaysIssueNewRefreshToken`', () => { + const alwaysIssueNewRefreshToken = true; + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 123, + model, + refreshTokenLifetime: 120, + alwaysIssueNewRefreshToken, + }); + + handler.alwaysIssueNewRefreshToken.should.equal( + alwaysIssueNewRefreshToken, + ); + }); + + it('should set the `alwaysIssueNewRefreshToken` to false', () => { + const alwaysIssueNewRefreshToken = false; + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 123, + model, + refreshTokenLifetime: 120, + alwaysIssueNewRefreshToken, + }); + + handler.alwaysIssueNewRefreshToken.should.equal( + alwaysIssueNewRefreshToken, + ); + }); + + it('should return the default `alwaysIssueNewRefreshToken` value', () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 123, + model, + refreshTokenLifetime: 120, + }); + + handler.alwaysIssueNewRefreshToken.should.equal(true); + }); + + it('should set the `extendedGrantTypes`', () => { + const extendedGrantTypes = { foo: 'bar' }; + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + extendedGrantTypes, + model, + refreshTokenLifetime: 120, + }); + + handler.grantTypes.should.containEql(extendedGrantTypes); + }); + + it('should set the `model`', () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + + handler.model.should.equal(model); + }); + + it('should set the `refreshTokenLifetime`', () => { + const refreshTokenLifetime = {}; + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime, + }); + + handler.refreshTokenLifetime.should.equal(refreshTokenLifetime); + }); + }); + + describe('handle()', () => { + it('should throw an error if `request` is missing', async () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + + try { + await handler.handle(undefined, undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: `request` must be an instance of Request', + ); + } + }); + + it('should throw an error if `response` is missing', async () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + await handler.handle(request, undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: `response` must be an instance of Response', + ); + } + }); + + it('should throw an error if the method is not `POST`', () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: {}, + headers: {}, + method: 'GET', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Invalid request: method must be POST'); + }); + }); + + it('should throw an error if the media type is not `application/x-www-form-urlencoded`', () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: {}, + headers: {}, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal( + 'Invalid request: content must be application/x-www-form-urlencoded', + ); + }); + }); + + it('should throw the error if an oauth error is thrown', () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: {}, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(InvalidClientError); + e.message.should.equal( + 'Invalid client: cannot retrieve client credentials', + ); + }); + }); + + it('should throw a server error if a non-oauth error is thrown', () => { + const model = { + getClient() { + throw new Error('Unhandled exception'); + }, + getUser() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { + client_id: 12345, + client_secret: 'secret', + grant_type: 'password', + password: 'bar', + username: 'foo', + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(ServerError); + e.message.should.equal('Unhandled exception'); + e.inner.should.be.an.instanceOf(Error); + }); + }); + + it('should update the response if an error is thrown', () => { + const model = { + getClient() { + throw new Error('Unhandled exception'); + }, + getUser() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { + client_id: 12345, + client_secret: 'secret', + grant_type: 'password', + password: 'bar', + username: 'foo', + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(() => { + response.body.should.eql({ + error: 'server_error', + error_description: 'Unhandled exception', + }); + response.status.should.equal(500); + }); + }); + + it('should return a bearer token if successful', async () => { + const token = { + accessToken: 'foo', + client: {}, + refreshToken: 'bar', + scope: 'foobar', + user: {}, + }; + const model = { + getClient() { + return { grants: ['password'] }; + }, + getUser() { + return {}; + }, + saveToken() { + return token; + }, + validateScope() { + return 'baz'; + }, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { + client_id: 12345, + client_secret: 'secret', + username: 'foo', + password: 'bar', + grant_type: 'password', + scope: 'baz', + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + try { + const data = await handler.handle(request, response); + data.should.eql(token); + } catch (error) { + should.fail('should.fail', ''); + } + }); + + it('should not return custom attributes in a bearer token if the allowExtendedTokenAttributes is not set', () => { + const token = { + accessToken: 'foo', + client: {}, + refreshToken: 'bar', + scope: 'foobar', + user: {}, + foo: 'bar', + }; + const model = { + getClient() { + return { grants: ['password'] }; + }, + getUser() { + return {}; + }, + saveToken() { + return token; + }, + validateScope() { + return 'baz'; + }, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { + client_id: 12345, + client_secret: 'secret', + username: 'foo', + password: 'bar', + grant_type: 'password', + scope: 'baz', + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .handle(request, response) + .then(() => { + should.exist(response.body.access_token); + should.exist(response.body.refresh_token); + should.exist(response.body.token_type); + should.exist(response.body.scope); + should.not.exist(response.body.foo); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + + it('should return custom attributes in a bearer token if the allowExtendedTokenAttributes is set', async () => { + const token = { + accessToken: 'foo', + client: {}, + refreshToken: 'bar', + scope: 'foobar', + user: {}, + foo: 'bar', + }; + const model = { + getClient() { + return { grants: ['password'] }; + }, + getUser() { + return {}; + }, + saveToken() { + return token; + }, + validateScope() { + return 'baz'; + }, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + allowExtendedTokenAttributes: true, + }); + const request = new Request({ + body: { + client_id: 12345, + client_secret: 'secret', + username: 'foo', + password: 'bar', + grant_type: 'password', + scope: 'baz', + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + await handler.handle(request, response); + should.exist(response.body.access_token); + should.exist(response.body.refresh_token); + should.exist(response.body.token_type); + should.exist(response.body.scope); + should.exist(response.body.foo); + }); + }); + + describe('getClient()', () => { + it('should throw an error if `clientId` is invalid', async () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { client_id: 'øå€£‰', client_secret: 'foo' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + await handler.getClient(request, undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Invalid parameter: `client_id`'); + } + }); + + it('should throw an error if `clientSecret` is invalid', async () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { client_id: 'foo', client_secret: 'øå€£‰' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + await handler.getClient(request, undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Invalid parameter: `client_secret`'); + } + }); + + it('should throw an error if `client` is missing', () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return handler + .getClient(request, undefined) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(InvalidClientError); + e.message.should.equal('Invalid client: client is invalid'); + }); + }); + + it('should throw an error if `client.grants` is missing', () => { + const model = { + getClient() { + return {}; + }, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return handler + .getClient(request, undefined) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(ServerError); + e.message.should.equal('Server error: missing client `grants`'); + }); + }); + + it('should throw an error if `client.grants` is invalid', async () => { + const model = { + getClient() { + return { grants: 'foobar' }; + }, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await handler.getClient(request, undefined); + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(ServerError); + e.message.should.equal('Server error: `grants` must be an array'); + } + }); + + it('should throw a 401 error if the client is invalid and the request contains an authorization header', () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: {}, + headers: { + authorization: util.format( + 'Basic %s', + Buffer.from('foo:bar').toString('base64'), + ), + }, + method: 'ANY', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + return handler + .getClient(request, response) + .then(() => { + should.fail('should.fail', ''); + }) + .catch(e => { + e.should.be.an.instanceOf(InvalidClientError); + e.code.should.equal(401); + e.message.should.equal('Invalid client: client is invalid'); + + response + .get('WWW-Authenticate') + .should.equal('Basic realm="Service"'); + }); + }); + + it('should return a client', async () => { + const client = { id: 12345, grants: [] }; + const model = { + getClient() { + return client; + }, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + const data = await handler.getClient(request, undefined); + data.should.equal(client); + } catch (error) { + should.fail('should.fail', ''); + } + }); + + describe('with `password` grant type and `requireClientAuthentication` is false', () => { + it('should return a client ', () => { + const client = { id: 12345, grants: [] }; + const model = { + async getClient() { + return client; + }, + saveToken() {}, + }; + + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + requireClientAuthentication: { + password: false, + }, + }); + const request = new Request({ + body: { client_id: 'blah', grant_type: 'password' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return handler + .getClient(request, undefined) + .then(data => { + data.should.equal(client); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + + describe('with `password` grant type and `requireClientAuthentication` is false and Authorization header', () => { + it('should return a client ', () => { + const client = { id: 12345, grants: [] }; + const model = { + async getClient() { + return client; + }, + saveToken() {}, + }; + + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + requireClientAuthentication: { + password: false, + }, + }); + const request = new Request({ + body: { grant_type: 'password' }, + headers: { + authorization: util.format( + 'Basic %s', + Buffer.from('blah:').toString('base64'), + ), + }, + method: 'ANY', + query: {}, + }); + + return handler + .getClient(request, undefined) + .then(data => { + data.should.equal(client); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + + it('should support promises', () => { + const model = { + getClient() { + return Promise.resolve({ grants: [] }); + }, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + handler.getClient(request, undefined).should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', () => { + const model = { + getClient() { + return { grants: [] }; + }, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + handler.getClient(request, undefined).should.be.an.instanceOf(Promise); + }); + + /* it('should support callbacks', () => { + const model = { + getClient(clientId, clientSecret, callback) { + callback(null, { grants: [] }); + }, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + handler.getClient(request, undefined).should.be.an.instanceOf(Promise); + }); */ + }); + + describe('getClientCredentials()', () => { + it('should throw an error if `client_id` is missing', () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { client_secret: 'foo' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + handler.getClientCredentials(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidClientError); + e.message.should.equal( + 'Invalid client: cannot retrieve client credentials', + ); + } + }); + + it('should throw an error if `client_secret` is missing', () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { client_id: 'foo' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + handler.getClientCredentials(request); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidClientError); + e.message.should.equal( + 'Invalid client: cannot retrieve client credentials', + ); + } + }); + + describe('with `client_id` and grant type is `password` and `requireClientAuthentication` is false', () => { + it('should return a client', () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + requireClientAuthentication: { password: false }, + }); + const request = new Request({ + body: { client_id: 'foo', grant_type: 'password' }, + headers: {}, + method: 'ANY', + query: {}, + }); + const credentials = handler.getClientCredentials(request); + + credentials.should.eql({ clientId: 'foo' }); + }); + }); + + describe('with `client_id` and `client_secret` in the request header as basic auth', () => { + it('should return a client', () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: {}, + headers: { + authorization: util.format( + 'Basic %s', + Buffer.from('foo:bar').toString('base64'), + ), + }, + method: 'ANY', + query: {}, + }); + const credentials = handler.getClientCredentials(request); + + credentials.should.eql({ clientId: 'foo', clientSecret: 'bar' }); + }); + }); + + describe('with `client_id` and `client_secret` in the request body', () => { + it('should return a client', () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { client_id: 'foo', client_secret: 'bar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + const credentials = handler.getClientCredentials(request); + + credentials.should.eql({ clientId: 'foo', clientSecret: 'bar' }); + }); + }); + }); + + describe('handleGrantType()', () => { + it('should throw an error if `grant_type` is missing', async () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + await handler.handleGrantType(request, undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Missing parameter: `grant_type`'); + } + }); + + it('should throw an error if `grant_type` is invalid', async () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { grant_type: '~foo~' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + await handler.handleGrantType(request, undefined); + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidRequestError); + e.message.should.equal('Invalid parameter: `grant_type`'); + } + }); + + it('should throw an error if `grant_type` is unsupported', async () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { grant_type: 'foobar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + await handler.handleGrantType(request, undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(UnsupportedGrantTypeError); + e.message.should.equal( + 'Unsupported grant type: `grant_type` is invalid', + ); + } + }); + + it('should throw an error if `grant_type` is unauthorized', async () => { + const client: any = { grants: ['client_credentials'] }; + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { grant_type: 'password' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + try { + await handler.handleGrantType(request, client); + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(UnauthorizedClientError); + e.message.should.equal('Unauthorized client: `grant_type` is invalid'); + } + }); + + /* it('should throw an invalid grant error if a non-oauth error is thrown', () => { + const client = { grants: ['password'] }; + const model = { + getClient(clientId, password, callback) { + callback(null, client); + }, + getUser(uid, pwd, callback) { + callback(); + }, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { grant_type: 'password', username: 'foo', password: 'bar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return handler + .handleGrantType(request, client) + .then(() => should.fail('should.fail', '')) + .catch(e => { + e.should.be.an.instanceOf(InvalidGrantError); + e.message.should.equal('Invalid grant: user credentials are invalid'); + }); + }); */ + + describe('with grant_type `authorization_code`', () => { + it('should return a token', () => { + const client: any = { id: 'foobar', grants: ['authorization_code'] }; + const token = {}; + const model = { + getAuthorizationCode() { + return { + authorizationCode: 12345, + client: { id: 'foobar' }, + expiresAt: new Date(new Date().getTime() * 2), + user: {}, + }; + }, + getClient() {}, + saveToken() { + return token; + }, + validateScope() { + return 'foo'; + }, + revokeAuthorizationCode() { + return { + authorizationCode: 12345, + client: { id: 'foobar' }, + expiresAt: new Date(new Date().getTime() / 2), + user: {}, + }; + }, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { + code: 12345, + grant_type: 'authorization_code', + }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return handler.handleGrantType(request, client).then(data => { + data.should.equal(token); + }); + // .catch(() => { + // should.fail('should.fail', ''); + // }); + }); + }); + + describe('with grant_type `client_credentials`', () => { + it('should return a token', () => { + const client: any = { grants: ['client_credentials'] }; + const token = {}; + const model = { + getClient() {}, + getUserFromClient() { + return {}; + }, + saveToken() { + return token; + }, + validateScope() { + return 'foo'; + }, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { + grant_type: 'client_credentials', + scope: 'foo', + }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return handler + .handleGrantType(request, client) + .then(data => { + data.should.equal(token); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + + describe('with grant_type `password`', () => { + it('should return a token', () => { + const client: any = { grants: ['password'] }; + const token = {}; + const model = { + getClient() {}, + getUser() { + return {}; + }, + saveToken() { + return token; + }, + validateScope() { + return 'baz'; + }, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { + client_id: 12345, + client_secret: 'secret', + grant_type: 'password', + password: 'bar', + username: 'foo', + scope: 'baz', + }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return handler + .handleGrantType(request, client) + .then(data => { + data.should.equal(token); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + + describe('with grant_type `refresh_token`', () => { + it('should return a token', () => { + const client: any = { grants: ['refresh_token'] }; + const token = { accessToken: 'foo', client: {}, user: {} }; + const model = { + getClient() {}, + getRefreshToken() { + return { + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date().getTime() * 2), + user: {}, + }; + }, + saveToken() { + return token; + }, + revokeToken() { + return { + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date().getTime() / 2), + user: {}, + }; + }, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { + grant_type: 'refresh_token', + refresh_token: 12345, + }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return handler + .handleGrantType(request, client) + .then(data => { + data.should.equal(token); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + + describe('with custom grant_type', () => { + it('should return a token', () => { + const client: any = { + grants: ['urn:ietf:params:oauth:grant-type:saml2-bearer'], + }; + const token = {}; + const model = { + getClient() {}, + getUser() { + return {}; + }, + saveToken() { + return token; + }, + validateScope() { + return 'foo'; + }, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + extendedGrantTypes: { + 'urn:ietf:params:oauth:grant-type:saml2-bearer': PasswordGrantType, + }, + }); + const request = new Request({ + body: { + grant_type: 'urn:ietf:params:oauth:grant-type:saml2-bearer', + username: 'foo', + password: 'bar', + }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return handler + .handleGrantType(request, client) + .then(data => { + data.should.equal(token); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + }); + + describe('getAccessTokenLifetime()', () => { + it('should return the client access token lifetime', () => { + const client: any = { accessTokenLifetime: 60 }; + const model = { + getClient() { + return client; + }, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + + handler.getAccessTokenLifetime(client).should.equal(60); + }); + + it('should return the default access token lifetime', () => { + const client: any = {}; + const model = { + getClient() { + return client; + }, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + + handler.getAccessTokenLifetime(client).should.equal(120); + }); + }); + + describe('getRefreshTokenLifetime()', () => { + it('should return the client access token lifetime', () => { + const client: any = { refreshTokenLifetime: 60 }; + const model = { + getClient() { + return client; + }, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + + handler.getRefreshTokenLifetime(client).should.equal(60); + }); + + it('should return the default access token lifetime', () => { + const client: any = {}; + const model = { + getClient() { + return client; + }, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + + handler.getRefreshTokenLifetime(client).should.equal(120); + }); + }); + + describe('getTokenType()', () => { + it('should return a token type', () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const tokenType = handler.getTokenType({ + accessToken: 'foo', + refreshToken: 'bar', + scope: 'foobar', + }); + + tokenType.should.containEql({ + accessToken: 'foo', + accessTokenLifetime: undefined, + refreshToken: 'bar', + scope: 'foobar', + }); + }); + }); + + describe('updateSuccessResponse()', () => { + it('should set the `body`', () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const tokenType = new BearerTokenType( + 'foo', + 'bar' as any, + 'biz', + undefined, + undefined, + ); + const response = new Response({ body: {}, headers: {} }); + + handler.updateSuccessResponse(response, tokenType); + + response.body.should.eql({ + access_token: 'foo', + expires_in: 'bar', + refresh_token: 'biz', + token_type: 'Bearer', + }); + }); + + it('should set the `Cache-Control` header', () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const tokenType = new BearerTokenType( + 'foo', + 'bar' as any, + 'biz', + undefined, + undefined, + ); + const response = new Response({ body: {}, headers: {} }); + + handler.updateSuccessResponse(response, tokenType); + + response.get('Cache-Control').should.equal('no-store'); + }); + + it('should set the `Pragma` header', () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const tokenType = new BearerTokenType( + 'foo', + 'bar' as any, + 'biz', + undefined, + undefined, + ); + const response = new Response({ body: {}, headers: {} }); + + handler.updateSuccessResponse(response, tokenType); + + response.get('Pragma').should.equal('no-cache'); + }); + }); + + describe('updateErrorResponse()', () => { + it('should set the `body`', () => { + const error = new AccessDeniedError('Cannot request a token'); + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const response = new Response({ body: {}, headers: {} }); + + handler.updateErrorResponse(response, error); + + response.body.error.should.equal('access_denied'); + response.body.error_description.should.equal('Cannot request a token'); + }); + + it('should set the `status`', () => { + const error = new AccessDeniedError('Cannot request a token'); + const model = { + getClient() {}, + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const response = new Response({ body: {}, headers: {} }); + + handler.updateErrorResponse(response, error); + + response.status.should.equal(400); + }); + }); +}); diff --git a/test/integration/handlers/token-handler_test.js b/test/integration/handlers/token-handler_test.js deleted file mode 100644 index 50277c113..000000000 --- a/test/integration/handlers/token-handler_test.js +++ /dev/null @@ -1,1079 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var AccessDeniedError = require('../../../lib/errors/access-denied-error'); -var BearerTokenType = require('../../../lib/token-types/bearer-token-type'); -var InvalidArgumentError = require('../../../lib/errors/invalid-argument-error'); -var InvalidClientError = require('../../../lib/errors/invalid-client-error'); -var InvalidGrantError = require('../../../lib/errors/invalid-grant-error'); -var InvalidRequestError = require('../../../lib/errors/invalid-request-error'); -var PasswordGrantType = require('../../../lib/grant-types/password-grant-type'); -var Promise = require('bluebird'); -var Request = require('../../../lib/request'); -var Response = require('../../../lib/response'); -var ServerError = require('../../../lib/errors/server-error'); -var TokenHandler = require('../../../lib/handlers/token-handler'); -var UnauthorizedClientError = require('../../../lib/errors/unauthorized-client-error'); -var UnsupportedGrantTypeError = require('../../../lib/errors/unsupported-grant-type-error'); -var should = require('should'); -var util = require('util'); - -/** - * Test `TokenHandler` integration. - */ - -describe('TokenHandler integration', function() { - describe('constructor()', function() { - it('should throw an error if `options.accessTokenLifetime` is missing', function() { - try { - new TokenHandler(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `accessTokenLifetime`'); - } - }); - - it('should throw an error if `options.model` is missing', function() { - try { - new TokenHandler({ accessTokenLifetime: 120 }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `model`'); - } - }); - - it('should throw an error if `options.refreshTokenLifetime` is missing', function() { - try { - new TokenHandler({ accessTokenLifetime: 120, model: {} }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `refreshTokenLifetime`'); - } - }); - - it('should throw an error if the model does not implement `getClient()`', function() { - try { - new TokenHandler({ accessTokenLifetime: 120, model: {}, refreshTokenLifetime: 120 }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: model does not implement `getClient()`'); - } - }); - - it('should set the `accessTokenLifetime`', function() { - var accessTokenLifetime = {}; - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: accessTokenLifetime, model: model, refreshTokenLifetime: 120 }); - - handler.accessTokenLifetime.should.equal(accessTokenLifetime); - }); - - it('should set the `alwaysIssueNewRefreshToken`', function() { - var alwaysIssueNewRefreshToken = true; - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 123, model: model, refreshTokenLifetime: 120, alwaysIssueNewRefreshToken: alwaysIssueNewRefreshToken }); - - handler.alwaysIssueNewRefreshToken.should.equal(alwaysIssueNewRefreshToken); - }); - - it('should set the `alwaysIssueNewRefreshToken` to false', function() { - var alwaysIssueNewRefreshToken = false; - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 123, model: model, refreshTokenLifetime: 120, alwaysIssueNewRefreshToken: alwaysIssueNewRefreshToken }); - - handler.alwaysIssueNewRefreshToken.should.equal(alwaysIssueNewRefreshToken); - }); - - it('should return the default `alwaysIssueNewRefreshToken` value', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 123, model: model, refreshTokenLifetime: 120 }); - - handler.alwaysIssueNewRefreshToken.should.equal(true); - }); - - it('should set the `extendedGrantTypes`', function() { - var extendedGrantTypes = { foo: 'bar' }; - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, extendedGrantTypes: extendedGrantTypes, model: model, refreshTokenLifetime: 120 }); - - handler.grantTypes.should.containEql(extendedGrantTypes); - }); - - it('should set the `model`', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - - handler.model.should.equal(model); - }); - - it('should set the `refreshTokenLifetime`', function() { - var refreshTokenLifetime = {}; - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: refreshTokenLifetime }); - - handler.refreshTokenLifetime.should.equal(refreshTokenLifetime); - }); - }); - - describe('handle()', function() { - it('should throw an error if `request` is missing', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - - try { - handler.handle(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: `request` must be an instance of Request'); - } - }); - - it('should throw an error if `response` is missing', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - try { - handler.handle(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: `response` must be an instance of Response'); - } - }); - - it('should throw an error if the method is not `POST`', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: {}, headers: {}, method: 'GET', query: {} }); - var response = new Response({ body: {}, headers: {} }); - - return handler.handle(request, response) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Invalid request: method must be POST'); - }); - }); - - it('should throw an error if the media type is not `application/x-www-form-urlencoded`', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: {}, headers: {}, method: 'POST', query: {} }); - var response = new Response({ body: {}, headers: {} }); - - return handler.handle(request, response) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Invalid request: content must be application/x-www-form-urlencoded'); - }); - }); - - it('should throw the error if an oauth error is thrown', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: {}, headers: { 'content-type': 'application/x-www-form-urlencoded', 'transfer-encoding': 'chunked' }, method: 'POST', query: {} }); - var response = new Response({ body: {}, headers: {} }); - - return handler.handle(request, response) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidClientError); - e.message.should.equal('Invalid client: cannot retrieve client credentials'); - }); - }); - - it('should throw a server error if a non-oauth error is thrown', function() { - var model = { - getClient: function() { - throw new Error('Unhandled exception'); - }, - getUser: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ - body: { - client_id: 12345, - client_secret: 'secret', - grant_type: 'password', - password: 'bar', - username: 'foo' - }, - headers: { 'content-type': 'application/x-www-form-urlencoded', 'transfer-encoding': 'chunked' }, - method: 'POST', - query: {} - }); - var response = new Response({ body: {}, headers: {} }); - - return handler.handle(request, response) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(ServerError); - e.message.should.equal('Unhandled exception'); - e.inner.should.be.an.instanceOf(Error); - }); - }); - - it('should update the response if an error is thrown', function() { - var model = { - getClient: function() { - throw new Error('Unhandled exception'); - }, - getUser: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ - body: { - client_id: 12345, - client_secret: 'secret', - grant_type: 'password', - password: 'bar', - username: 'foo' - }, - headers: { 'content-type': 'application/x-www-form-urlencoded', 'transfer-encoding': 'chunked' }, - method: 'POST', - query: {} - }); - var response = new Response({ body: {}, headers: {} }); - - return handler.handle(request, response) - .then(should.fail) - .catch(function() { - response.body.should.eql({ error: 'server_error', error_description: 'Unhandled exception' }); - response.status.should.equal(503); - }); - }); - - it('should return a bearer token if successful', function() { - var token = { accessToken: 'foo', client: {}, refreshToken: 'bar', scope: 'foobar', user: {} }; - var model = { - getClient: function() { return { grants: ['password'] }; }, - getUser: function() { return {}; }, - saveToken: function() { return token; }, - validateScope: function() { return 'baz'; } - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ - body: { - client_id: 12345, - client_secret: 'secret', - username: 'foo', - password: 'bar', - grant_type: 'password', - scope: 'baz' - }, - headers: { 'content-type': 'application/x-www-form-urlencoded', 'transfer-encoding': 'chunked' }, - method: 'POST', - query: {} - }); - var response = new Response({ body: {}, headers: {} }); - - return handler.handle(request, response) - .then(function(data) { - data.should.eql(token); - }) - .catch(should.fail); - }); - - it('should not return custom attributes in a bearer token if the allowExtendedTokenAttributes is not set', function() { - var token = { accessToken: 'foo', client: {}, refreshToken: 'bar', scope: 'foobar', user: {}, foo: 'bar' }; - var model = { - getClient: function() { return { grants: ['password'] }; }, - getUser: function() { return {}; }, - saveToken: function() { return token; }, - validateScope: function() { return 'baz'; } - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ - body: { - client_id: 12345, - client_secret: 'secret', - username: 'foo', - password: 'bar', - grant_type: 'password', - scope: 'baz' - }, - headers: { 'content-type': 'application/x-www-form-urlencoded', 'transfer-encoding': 'chunked' }, - method: 'POST', - query: {} - }); - var response = new Response({ body: {}, headers: {} }); - - return handler.handle(request, response) - .then(function() { - should.exist(response.body.access_token); - should.exist(response.body.refresh_token); - should.exist(response.body.token_type); - should.exist(response.body.scope); - should.not.exist(response.body.foo); - }) - .catch(should.fail); - }); - - it('should return custom attributes in a bearer token if the allowExtendedTokenAttributes is set', function() { - var token = { accessToken: 'foo', client: {}, refreshToken: 'bar', scope: 'foobar', user: {}, foo: 'bar' }; - var model = { - getClient: function() { return { grants: ['password'] }; }, - getUser: function() { return {}; }, - saveToken: function() { return token; }, - validateScope: function() { return 'baz'; } - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120, allowExtendedTokenAttributes: true }); - var request = new Request({ - body: { - client_id: 12345, - client_secret: 'secret', - username: 'foo', - password: 'bar', - grant_type: 'password', - scope: 'baz' - }, - headers: { 'content-type': 'application/x-www-form-urlencoded', 'transfer-encoding': 'chunked' }, - method: 'POST', - query: {} - }); - var response = new Response({ body: {}, headers: {} }); - - return handler.handle(request, response) - .then(function() { - should.exist(response.body.access_token); - should.exist(response.body.refresh_token); - should.exist(response.body.token_type); - should.exist(response.body.scope); - should.exist(response.body.foo); - }) - .catch(should.fail); - }); - }); - - - describe('getClient()', function() { - it('should throw an error if `clientId` is invalid', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { client_id: 'øå€£‰', client_secret: 'foo' }, headers: {}, method: {}, query: {} }); - - try { - handler.getClient(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Invalid parameter: `client_id`'); - } - }); - - it('should throw an error if `clientSecret` is invalid', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { client_id: 'foo', client_secret: 'øå€£‰' }, headers: {}, method: {}, query: {} }); - - try { - handler.getClient(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Invalid parameter: `client_secret`'); - } - }); - - it('should throw an error if `client` is missing', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, method: {}, query: {} }); - - return handler.getClient(request) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidClientError); - e.message.should.equal('Invalid client: client is invalid'); - }); - }); - - it('should throw an error if `client.grants` is missing', function() { - var model = { - getClient: function() { return {}; }, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, method: {}, query: {} }); - - return handler.getClient(request) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(ServerError); - e.message.should.equal('Server error: missing client `grants`'); - }); - }); - - it('should throw an error if `client.grants` is invalid', function() { - var model = { - getClient: function() { return { grants: 'foobar' }; }, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, method: {}, query: {} }); - - return handler.getClient(request) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(ServerError); - e.message.should.equal('Server error: `grants` must be an array'); - }); - }); - - it('should throw a 401 error if the client is invalid and the request contains an authorization header', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ - body: {}, - headers: { 'authorization': util.format('Basic %s', new Buffer('foo:bar').toString('base64')) }, - method: {}, - query: {} - }); - var response = new Response({ body: {}, headers: {} }); - - return handler.getClient(request, response) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidClientError); - e.code.should.equal(401); - e.message.should.equal('Invalid client: client is invalid'); - - response.get('WWW-Authenticate').should.equal('Basic realm="Service"'); - }); - }); - - it('should return a client', function() { - var client = { id: 12345, grants: [] }; - var model = { - getClient: function() { return client; }, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, method: {}, query: {} }); - - return handler.getClient(request) - .then(function(data) { - data.should.equal(client); - }) - .catch(should.fail); - }); - - describe('with `password` grant type and `requireClientAuthentication` is false', function() { - - it('should return a client ', function() { - var client = { id: 12345, grants: [] }; - var model = { - getClient: function() { return client; }, - saveToken: function() {} - }; - - var handler = new TokenHandler({ - accessTokenLifetime: 120, - model: model, - refreshTokenLifetime: 120, - requireClientAuthentication: { - password: false - } - }); - var request = new Request({ body: { client_id: 'blah', grant_type: 'password'}, headers: {}, method: {}, query: {} }); - - return handler.getClient(request) - .then(function(data) { - data.should.equal(client); - }) - .catch(should.fail); - }); - }); - - describe('with `password` grant type and `requireClientAuthentication` is false and Authorization header', function() { - - it('should return a client ', function() { - var client = { id: 12345, grants: [] }; - var model = { - getClient: function() { return client; }, - saveToken: function() {} - }; - - var handler = new TokenHandler({ - accessTokenLifetime: 120, - model: model, - refreshTokenLifetime: 120, - requireClientAuthentication: { - password: false - } - }); - var request = new Request({ - body: { grant_type: 'password'}, - headers: { 'authorization': util.format('Basic %s', new Buffer('blah:').toString('base64')) }, - method: {}, - query: {} - }); - - return handler.getClient(request) - .then(function(data) { - data.should.equal(client); - }) - .catch(should.fail); - }); - }); - - it('should support promises', function() { - var model = { - getClient: function() { return Promise.resolve({ grants: [] }); }, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, method: {}, query: {} }); - - handler.getClient(request).should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var model = { - getClient: function() { return { grants: [] }; }, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, method: {}, query: {} }); - - handler.getClient(request).should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function() { - var model = { - getClient: function(clientId, clientSecret, callback) { callback(null, { grants: [] }); }, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, method: {}, query: {} }); - - handler.getClient(request).should.be.an.instanceOf(Promise); - }); - }); - - describe('getClientCredentials()', function() { - it('should throw an error if `client_id` is missing', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { client_secret: 'foo' }, headers: {}, method: {}, query: {} }); - - try { - handler.getClientCredentials(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidClientError); - e.message.should.equal('Invalid client: cannot retrieve client credentials'); - } - }); - - it('should throw an error if `client_secret` is missing', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { client_id: 'foo' }, headers: {}, method: {}, query: {} }); - - try { - handler.getClientCredentials(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidClientError); - e.message.should.equal('Invalid client: cannot retrieve client credentials'); - } - }); - - describe('with `client_id` and grant type is `password` and `requireClientAuthentication` is false', function() { - it('should return a client', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120, requireClientAuthentication: { password: false} }); - var request = new Request({ body: { client_id: 'foo', grant_type: 'password' }, headers: {}, method: {}, query: {} }); - var credentials = handler.getClientCredentials(request); - - credentials.should.eql({ clientId: 'foo' }); - }); - }); - - describe('with `client_id` and `client_secret` in the request header as basic auth', function() { - it('should return a client', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ - body: {}, - headers: { - 'authorization': util.format('Basic %s', new Buffer('foo:bar').toString('base64')) - }, - method: {}, - query: {} - }); - var credentials = handler.getClientCredentials(request); - - credentials.should.eql({ clientId: 'foo', clientSecret: 'bar' }); - }); - }); - - describe('with `client_id` and `client_secret` in the request body', function() { - it('should return a client', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { client_id: 'foo', client_secret: 'bar' }, headers: {}, method: {}, query: {} }); - var credentials = handler.getClientCredentials(request); - - credentials.should.eql({ clientId: 'foo', clientSecret: 'bar' }); - }); - }); - }); - - describe('handleGrantType()', function() { - it('should throw an error if `grant_type` is missing', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - try { - handler.handleGrantType(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Missing parameter: `grant_type`'); - } - }); - - it('should throw an error if `grant_type` is invalid', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { grant_type: '~foo~' }, headers: {}, method: {}, query: {} }); - - try { - handler.handleGrantType(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidRequestError); - e.message.should.equal('Invalid parameter: `grant_type`'); - } - }); - - it('should throw an error if `grant_type` is unsupported', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { grant_type: 'foobar' }, headers: {}, method: {}, query: {} }); - - try { - handler.handleGrantType(request); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(UnsupportedGrantTypeError); - e.message.should.equal('Unsupported grant type: `grant_type` is invalid'); - } - }); - - it('should throw an error if `grant_type` is unauthorized', function() { - var client = { grants: ['client_credentials'] }; - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { grant_type: 'password' }, headers: {}, method: {}, query: {} }); - - try { - handler.handleGrantType(request, client); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(UnauthorizedClientError); - e.message.should.equal('Unauthorized client: `grant_type` is invalid'); - } - }); - - it('should throw an invalid grant error if a non-oauth error is thrown', function() { - var client = { grants: ['password'] }; - var model = { - getClient: function(clientId, password, callback) { callback(null, client); }, - getUser: function(uid, pwd, callback) { callback(); }, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { grant_type: 'password', username: 'foo', password: 'bar' }, headers: {}, method: {}, query: {} }); - - return handler.handleGrantType(request, client) - .then(should.fail) - .catch(function(e) { - e.should.be.an.instanceOf(InvalidGrantError); - e.message.should.equal('Invalid grant: user credentials are invalid'); - }); - }); - - describe('with grant_type `authorization_code`', function() { - it('should return a token', function() { - var client = { id: 'foobar', grants: ['authorization_code'] }; - var token = {}; - var model = { - getAuthorizationCode: function() { return { authorizationCode: 12345, client: { id: 'foobar' }, expiresAt: new Date(new Date() * 2), user: {} }; }, - getClient: function() {}, - saveToken: function() { return token; }, - validateScope: function() { return 'foo'; }, - revokeAuthorizationCode: function() { return { authorizationCode: 12345, client: { id: 'foobar' }, expiresAt: new Date(new Date() / 2), user: {} }; } - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ - body: { - code: 12345, - grant_type: 'authorization_code' - }, - headers: {}, - method: {}, - query: {} - }); - - return handler.handleGrantType(request, client) - .then(function(data) { - data.should.equal(token); - }) - .catch(should.fail); - }); - }); - - describe('with grant_type `client_credentials`', function() { - it('should return a token', function() { - var client = { grants: ['client_credentials'] }; - var token = {}; - var model = { - getClient: function() {}, - getUserFromClient: function() { return {}; }, - saveToken: function() { return token; }, - validateScope: function() { return 'foo'; } - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ - body: { - grant_type: 'client_credentials', - scope: 'foo' - }, - headers: {}, - method: {}, - query: {} - }); - - return handler.handleGrantType(request, client) - .then(function(data) { - data.should.equal(token); - }) - .catch(should.fail); - }); - }); - - describe('with grant_type `password`', function() { - it('should return a token', function() { - var client = { grants: ['password'] }; - var token = {}; - var model = { - getClient: function() {}, - getUser: function() { return {}; }, - saveToken: function() { return token; }, - validateScope: function() { return 'baz'; } - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ - body: { - client_id: 12345, - client_secret: 'secret', - grant_type: 'password', - password: 'bar', - username: 'foo', - scope: 'baz' - }, - headers: {}, - method: {}, - query: {} - }); - - return handler.handleGrantType(request, client) - .then(function(data) { - data.should.equal(token); - }) - .catch(should.fail); - }); - }); - - describe('with grant_type `refresh_token`', function() { - it('should return a token', function() { - var client = { grants: ['refresh_token'] }; - var token = { accessToken: 'foo', client: {}, user: {} }; - var model = { - getClient: function() {}, - getRefreshToken: function() { return { accessToken: 'foo', client: {}, refreshTokenExpiresAt: new Date(new Date() * 2), user: {} }; }, - saveToken: function() { return token; }, - revokeToken: function() { return { accessToken: 'foo', client: {}, refreshTokenExpiresAt: new Date(new Date() / 2), user: {} }; } - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ - body: { - grant_type: 'refresh_token', - refresh_token: 12345 - }, - headers: {}, - method: {}, - query: {} - }); - - return handler.handleGrantType(request, client) - .then(function(data) { - data.should.equal(token); - }) - .catch(should.fail); - }); - }); - - describe('with custom grant_type', function() { - it('should return a token', function() { - var client = { grants: ['urn:ietf:params:oauth:grant-type:saml2-bearer'] }; - var token = {}; - var model = { - getClient: function() {}, - getUser: function() { return {}; }, - saveToken: function() { return token; }, - validateScope: function() { return 'foo'; } - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120, extendedGrantTypes: { 'urn:ietf:params:oauth:grant-type:saml2-bearer': PasswordGrantType } }); - var request = new Request({ body: { grant_type: 'urn:ietf:params:oauth:grant-type:saml2-bearer', username: 'foo', password: 'bar' }, headers: {}, method: {}, query: {} }); - - return handler.handleGrantType(request, client) - .then(function(data) { - data.should.equal(token); - }) - .catch(should.fail); - }); - }); - }); - - describe('getAccessTokenLifetime()', function() { - it('should return the client access token lifetime', function() { - var client = { accessTokenLifetime: 60 }; - var model = { - getClient: function() { return client; }, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - - handler.getAccessTokenLifetime(client).should.equal(60); - }); - - it('should return the default access token lifetime', function() { - var client = {}; - var model = { - getClient: function() { return client; }, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - - handler.getAccessTokenLifetime(client).should.equal(120); - }); - }); - - describe('getRefreshTokenLifetime()', function() { - it('should return the client access token lifetime', function() { - var client = { refreshTokenLifetime: 60 }; - var model = { - getClient: function() { return client; }, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - - handler.getRefreshTokenLifetime(client).should.equal(60); - }); - - it('should return the default access token lifetime', function() { - var client = {}; - var model = { - getClient: function() { return client; }, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - - handler.getRefreshTokenLifetime(client).should.equal(120); - }); - }); - - describe('getTokenType()', function() { - it('should return a token type', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var tokenType = handler.getTokenType({ accessToken: 'foo', refreshToken: 'bar', scope: 'foobar' }); - - tokenType.should.containEql({ accessToken: 'foo', accessTokenLifetime: undefined, refreshToken: 'bar', scope: 'foobar' }); - }); - }); - - describe('updateSuccessResponse()', function() { - it('should set the `body`', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var tokenType = new BearerTokenType('foo', 'bar', 'biz'); - var response = new Response({ body: {}, headers: {} }); - - handler.updateSuccessResponse(response, tokenType); - - response.body.should.eql({ access_token: 'foo', expires_in: 'bar', refresh_token: 'biz', token_type: 'Bearer' }); - }); - - it('should set the `Cache-Control` header', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var tokenType = new BearerTokenType('foo', 'bar', 'biz'); - var response = new Response({ body: {}, headers: {} }); - - handler.updateSuccessResponse(response, tokenType); - - response.get('Cache-Control').should.equal('no-store'); - }); - - it('should set the `Pragma` header', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var tokenType = new BearerTokenType('foo', 'bar', 'biz'); - var response = new Response({ body: {}, headers: {} }); - - handler.updateSuccessResponse(response, tokenType); - - response.get('Pragma').should.equal('no-cache'); - }); - }); - - describe('updateErrorResponse()', function() { - it('should set the `body`', function() { - var error = new AccessDeniedError('Cannot request a token'); - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var response = new Response({ body: {}, headers: {} }); - - handler.updateErrorResponse(response, error); - - response.body.error.should.equal('access_denied'); - response.body.error_description.should.equal('Cannot request a token'); - }); - - it('should set the `status`', function() { - var error = new AccessDeniedError('Cannot request a token'); - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var response = new Response({ body: {}, headers: {} }); - - handler.updateErrorResponse(response, error); - - response.status.should.equal(400); - }); - }); -}); diff --git a/test/integration/request.spec.ts b/test/integration/request.spec.ts new file mode 100755 index 000000000..9515ed7b3 --- /dev/null +++ b/test/integration/request.spec.ts @@ -0,0 +1,183 @@ +import * as should from 'should'; +import { InvalidArgumentError } from '../../lib/errors'; +import { Request } from '../../lib/request'; + +/** + * Test `Request` integration. + */ + +describe('Request integration', () => { + describe('constructor()', () => { + it('should throw an error if `headers` is missing', () => { + try { + new Request({ body: {} } as any); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `headers`'); + } + }); + + it('should throw an error if `method` is missing', () => { + try { + new Request({ body: {}, headers: {} } as any); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `method`'); + } + }); + + it('should throw an error if `query` is missing', () => { + try { + new Request({ body: {}, headers: {}, method: 'ANY' } as any); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `query`'); + } + }); + + it('should set the `body`', () => { + const request = new Request({ + body: 'foo', + headers: {}, + method: 'ANY', + query: {}, + }); + + request.body.should.equal('foo'); + }); + + it('should set the `headers`', () => { + const request = new Request({ + body: {}, + headers: { foo: 'bar', QuX: 'biz' }, + method: 'ANY', + query: {}, + }); + + request.headers.should.eql({ foo: 'bar', qux: 'biz' }); + }); + + it('should set the `method`', () => { + const request = new Request({ + body: {}, + headers: {}, + method: 'biz', + query: {}, + }); + + request.method.should.equal('BIZ'); + }); + + it('should set the `query`', () => { + const request = new Request({ + body: {}, + headers: {}, + method: 'ANY', + query: 'baz', + }); + + request.query.should.equal('baz'); + }); + }); + + describe('get()', () => { + it('should return `undefined` if the field does not exist', () => { + const request = new Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + + (request.get('content-type') === undefined).should.be.true(); + }); + + it('should return the value if the field exists', () => { + const request = new Request({ + body: {}, + headers: { + 'content-type': 'text/html; charset=utf-8', + }, + method: 'ANY', + query: {}, + }); + + request.get('Content-Type').should.equal('text/html; charset=utf-8'); + }); + }); + + describe('is()', () => { + it('should accept an array of `types`', () => { + const request = new Request({ + body: {}, + headers: { + 'content-type': 'application/json', + 'transfer-encoding': 'chunked', + }, + method: 'ANY', + query: {}, + }); + + request.is(['html', 'json']).should.equal('json'); + }); + + it('should accept multiple `types` as arguments', () => { + const request = new Request({ + body: {}, + headers: { + 'content-type': 'application/json', + 'transfer-encoding': 'chunked', + }, + method: 'ANY', + query: {}, + }); + + request.is('html', 'json').should.equal('json'); + }); + + it('should return the first matching type', () => { + const request = new Request({ + body: {}, + headers: { + 'content-type': 'text/html; charset=utf-8', + 'transfer-encoding': 'chunked', + }, + method: 'ANY', + query: {}, + }); + + request.is('html').should.equal('html'); + }); + + it('should return `false` if none of the `types` match', () => { + const request = new Request({ + body: {}, + headers: { + 'content-type': 'text/html; charset=utf-8', + 'transfer-encoding': 'chunked', + }, + method: 'ANY', + query: {}, + }); + + request.is('json').should.be.false(); + }); + + it('should return `false` if the request has no body', () => { + const request = new Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + + request.is('text/html').should.be.false(); + }); + }); +}); diff --git a/test/integration/request_test.js b/test/integration/request_test.js deleted file mode 100644 index a43527671..000000000 --- a/test/integration/request_test.js +++ /dev/null @@ -1,159 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var Request = require('../../lib/request'); -var InvalidArgumentError = require('../../lib/errors/invalid-argument-error'); -var should = require('should'); - -/** - * Test `Request` integration. - */ - -describe('Request integration', function() { - describe('constructor()', function() { - it('should throw an error if `headers` is missing', function() { - try { - new Request({ body: {} }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `headers`'); - } - }); - - it('should throw an error if `method` is missing', function() { - try { - new Request({ body: {}, headers: {} }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `method`'); - } - }); - - it('should throw an error if `query` is missing', function() { - try { - new Request({ body: {}, headers: {}, method: {} }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `query`'); - } - }); - - it('should set the `body`', function() { - var request = new Request({ body: 'foo', headers: {}, method: {}, query: {} }); - - request.body.should.equal('foo'); - }); - - it('should set the `headers`', function() { - var request = new Request({ body: {}, headers: { foo: 'bar', QuX: 'biz' }, method: {}, query: {} }); - - request.headers.should.eql({ foo: 'bar', qux: 'biz' }); - }); - - it('should set the `method`', function() { - var request = new Request({ body: {}, headers: {}, method: 'biz', query: {} }); - - request.method.should.equal('biz'); - }); - - it('should set the `query`', function() { - var request = new Request({ body: {}, headers: {}, method: {}, query: 'baz' }); - - request.query.should.equal('baz'); - }); - }); - - describe('get()', function() { - it('should return `undefined` if the field does not exist', function() { - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - (undefined === request.get('content-type')).should.be.true; - }); - - it('should return the value if the field exists', function() { - var request = new Request({ - body: {}, - headers: { - 'content-type': 'text/html; charset=utf-8' - }, - method: {}, - query: {} - }); - - request.get('Content-Type').should.equal('text/html; charset=utf-8'); - }); - }); - - describe('is()', function() { - it('should accept an array of `types`', function() { - var request = new Request({ - body: {}, - headers: { - 'content-type': 'application/json', - 'transfer-encoding': 'chunked' - }, - method: {}, - query: {} - }); - - request.is(['html', 'json']).should.equal('json'); - }); - - it('should accept multiple `types` as arguments', function() { - var request = new Request({ - body: {}, - headers: { - 'content-type': 'application/json', - 'transfer-encoding': 'chunked' - }, - method: {}, - query: {} - }); - - request.is('html', 'json').should.equal('json'); - }); - - it('should return the first matching type', function() { - var request = new Request({ - body: {}, - headers: { - 'content-type': 'text/html; charset=utf-8', - 'transfer-encoding': 'chunked' - }, - method: {}, - query: {} - }); - - request.is('html').should.equal('html'); - }); - - it('should return `false` if none of the `types` match', function() { - var request = new Request({ - body: {}, - headers: { - 'content-type': 'text/html; charset=utf-8', - 'transfer-encoding': 'chunked' - }, - method: {}, - query: {} - }); - - request.is('json').should.be.false; - }); - - it('should return `false` if the request has no body', function() { - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - - request.is('text/html').should.be.false; - }); - }); -}); diff --git a/test/integration/response-types/code-response-type.spec.ts b/test/integration/response-types/code-response-type.spec.ts new file mode 100755 index 000000000..1824a7f51 --- /dev/null +++ b/test/integration/response-types/code-response-type.spec.ts @@ -0,0 +1,339 @@ +import * as should from 'should'; +import * as sinon from 'sinon'; +import * as url from 'url'; +import { InvalidArgumentError } from '../../../lib/errors'; +import { CodeResponseType } from '../../../lib/response-types'; + +/** + * Test `CodeResponseType` integration. + */ + +describe('CodeResponseType integration', () => { + describe('constructor()', () => { + it('should throw an error if `options.authorizationCodeLifetime` is missing', () => { + try { + new CodeResponseType(); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Missing parameter: `authorizationCodeLifetime`', + ); + } + }); + + it('should set the `code`', () => { + const model = { + saveAuthorizationCode: () => {}, + }; + const responseType = new CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + + responseType.authorizationCodeLifetime.should.equal(120); + }); + }); + + it('should throw an error if the model does not implement `saveAuthorizationCode()`', () => { + try { + new CodeResponseType({ authorizationCodeLifetime: 120, model: {} }); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal( + 'Invalid argument: model does not implement `saveAuthorizationCode()`', + ); + } + }); + + it('should set the `authorizationCodeLifetime`', () => { + const model = { + saveAuthorizationCode: () => {}, + }; + const handler = new CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + + handler.authorizationCodeLifetime.should.equal(120); + }); + + describe('buildRedirectUri()', () => { + it('should throw an error if the `redirectUri` is missing', () => { + const model = { + saveAuthorizationCode: () => {}, + }; + const responseType = new CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + + try { + responseType.buildRedirectUri(undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `redirectUri`'); + } + }); + + it('should return the new redirect uri and set the `code` and `state` in the query', () => { + const model = { + saveAuthorizationCode: () => {}, + }; + const responseType = new CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + responseType.code = 'foo'; + const redirectUri = responseType.buildRedirectUri( + url.parse('http://example.com/cb'), + ); + + url.format(redirectUri).should.equal('http://example.com/cb?code=foo'); + }); + + it('should return the new redirect uri and append the `code` and `state` in the query', () => { + const model = { + saveAuthorizationCode: () => {}, + }; + const responseType = new CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + responseType.code = 'foo'; + const redirectUri = responseType.buildRedirectUri( + url.parse('http://example.com/cb?foo=bar', true), + ); + + url + .format(redirectUri) + .should.equal('http://example.com/cb?foo=bar&code=foo'); + }); + }); + + it('should set the `model`', () => { + const model = { + saveAuthorizationCode: () => {}, + }; + const handler = new CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + + handler.model.should.equal(model); + }); + + describe('generateAuthorizationCode()', () => { + it('should return an auth code', () => { + const model = { + getAccessToken: () => {}, + getClient: () => {}, + saveAuthorizationCode: () => {}, + }; + const handler = new CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + + return handler + .generateAuthorizationCode(undefined, undefined, undefined) + .then((data: any) => { + data.should.be.a.sha1(); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + + it('should support promises', () => { + const model = { + generateAuthorizationCode: () => { + return Promise.resolve({}); + }, + getAccessToken: () => {}, + getClient: () => {}, + saveAuthorizationCode: () => {}, + }; + const handler = new CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + + handler + .generateAuthorizationCode(undefined, undefined, undefined) + .should.be.an.instanceOf(Promise); + }); + + // it('should support non-promises', () => { + // const model = { + // generateAuthorizationCode: () => { + // return {}; + // }, + // getAccessToken: () => {}, + // getClient: () => {}, + // saveAuthorizationCode: () => {}, + // }; + // const handler = new CodeResponseType({ + // authorizationCodeLifetime: 120, + // model, + // }); + + // handler + // .generateAuthorizationCode(undefined, undefined, undefined) + // .should.be.an.instanceOf(Promise); + // }); + }); + + describe('getAuthorizationCodeExpiresAt()', () => { + it('should return a date', () => { + const model = { + getAccessToken: () => {}, + getClient: () => {}, + saveAuthorizationCode: () => {}, + }; + const handler: any = new CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + + handler.getAuthorizationCodeExpiresAt({}).should.be.an.instanceOf(Date); + }); + }); + + describe('saveAuthorizationCode()', () => { + it('should return an auth code', () => { + const authorizationCode = {}; + const model = { + getAccessToken: () => {}, + getClient: () => {}, + saveAuthorizationCode: () => { + return authorizationCode; + }, + }; + const handler: any = new CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + + return handler + .saveAuthorizationCode('foo', 'bar', 'biz', 'baz') + .then(data => { + data.should.equal(authorizationCode); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + + it('should support promises when calling `model.saveAuthorizationCode()`', () => { + const model = { + getAccessToken: () => {}, + getClient: () => {}, + saveAuthorizationCode: () => { + return Promise.resolve({}); + }, + }; + const handler: any = new CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + + handler + .saveAuthorizationCode('foo', 'bar', 'biz', 'baz', undefined, undefined) + .should.be.an.instanceOf(Promise); + }); + + // it('should support non-promises when calling `model.saveAuthorizationCode()`', () => { + // const model = { + // getAccessToken: () => {}, + // getClient: () => {}, + // saveAuthorizationCode: () => { + // return {}; + // }, + // }; + // const handler = new CodeResponseType({ + // authorizationCodeLifetime: 120, + // model, + // }); + + // handler + // .saveAuthorizationCode( + // 'foo', + // 'bar' as any, + // 'biz', + // 'baz' as any, + // undefined, + // undefined, + // ) + // .should.be.an.instanceOf(Promise); + // }); + }); + + describe('saveAuthorizationCode()', () => { + it('should call `model.saveAuthorizationCode()`', () => { + const model = { + getAccessToken: () => {}, + getClient: () => {}, + saveAuthorizationCode: sinon.stub().returns({}), + }; + const handler = new CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + + return handler + .saveAuthorizationCode( + 'foo', + 'bar' as any, + 'qux', + 'biz' as any, + 'baz', + 'boz' as any, + ) + .then(() => { + model.saveAuthorizationCode.callCount.should.equal(1); + model.saveAuthorizationCode.firstCall.args.should.have.length(3); + model.saveAuthorizationCode.firstCall.args[0].should.eql({ + authorizationCode: 'foo', + expiresAt: 'bar', + redirectUri: 'baz', + scope: 'qux', + }); + model.saveAuthorizationCode.firstCall.args[1].should.equal('biz'); + model.saveAuthorizationCode.firstCall.args[2].should.equal('boz'); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + + describe('generateAuthorizationCode()', () => { + it('should call `model.generateAuthorizationCode()`', () => { + const model = { + generateAuthorizationCode: sinon.stub().returns({}), + getAccessToken: () => {}, + getClient: () => {}, + saveAuthorizationCode: () => {}, + }; + const handler = new CodeResponseType({ + authorizationCodeLifetime: 120, + model, + }); + + return handler + .generateAuthorizationCode(undefined, undefined, undefined) + .then(() => { + model.generateAuthorizationCode.callCount.should.equal(1); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); +}); diff --git a/test/integration/response-types/code-response-type_test.js b/test/integration/response-types/code-response-type_test.js deleted file mode 100644 index 5461b62c4..000000000 --- a/test/integration/response-types/code-response-type_test.js +++ /dev/null @@ -1,64 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var CodeResponseType = require('../../../lib/response-types/code-response-type'); -var InvalidArgumentError = require('../../../lib/errors/invalid-argument-error'); -var should = require('should'); -var url = require('url'); - -/** - * Test `CodeResponseType` integration. - */ - -describe('CodeResponseType integration', function() { - describe('constructor()', function() { - it('should throw an error if `code` is missing', function() { - try { - new CodeResponseType(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `code`'); - } - }); - - it('should set the `code`', function() { - var responseType = new CodeResponseType('foo'); - - responseType.code.should.equal('foo'); - }); - }); - - describe('buildRedirectUri()', function() { - it('should throw an error if the `redirectUri` is missing', function() { - var responseType = new CodeResponseType('foo'); - - try { - responseType.buildRedirectUri(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `redirectUri`'); - } - }); - - it('should return the new redirect uri and set the `code` and `state` in the query', function() { - var responseType = new CodeResponseType('foo'); - var redirectUri = responseType.buildRedirectUri('http://example.com/cb'); - - url.format(redirectUri).should.equal('http://example.com/cb?code=foo'); - }); - - it('should return the new redirect uri and append the `code` and `state` in the query', function() { - var responseType = new CodeResponseType('foo'); - var redirectUri = responseType.buildRedirectUri('http://example.com/cb?foo=bar'); - - url.format(redirectUri).should.equal('http://example.com/cb?foo=bar&code=foo'); - }); - }); -}); diff --git a/test/integration/response-types/token-response-type.spec.ts b/test/integration/response-types/token-response-type.spec.ts new file mode 100644 index 000000000..97847c8a6 --- /dev/null +++ b/test/integration/response-types/token-response-type.spec.ts @@ -0,0 +1,96 @@ +import * as should from 'should'; +import * as url from 'url'; +import { InvalidArgumentError } from '../../../lib/errors'; +import { TokenResponseType } from '../../../lib/response-types'; + +/** + * Test `TokenResponseType` integration. + */ + +describe('TokenResponseType integration', () => { + describe('constructor()', () => { + it('should throw an error if `options.accessTokenLifetime` is missing', () => { + try { + new TokenResponseType(); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `accessTokenLifetime`'); + } + }); + + it('should set `accessTokenLifetime`', () => { + const responseType = new TokenResponseType({ + accessTokenLifetime: 120, + model: {}, + }); + + responseType.accessTokenLifetime.should.equal(120); + }); + + it('should set the `model`', () => { + const model = { + foobar() {}, + }; + const handler = new TokenResponseType({ + accessTokenLifetime: 120, + model, + }); + + handler.model.should.equal(model); + }); + }); + + describe('buildRedirectUri()', () => { + it('should throw an error if the `redirectUri` is missing', () => { + const responseType = new TokenResponseType({ + accessTokenLifetime: 120, + model: {}, + }); + + try { + responseType.buildRedirectUri(undefined); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `redirectUri`'); + } + }); + + it('should return the new redirect uri and set `access_token` and `state` in the query', () => { + const responseType = new TokenResponseType({ + accessTokenLifetime: 120, + model: {}, + }); + + responseType.accessToken = 'foobar-token'; + const redirectUri = responseType.buildRedirectUri( + url.parse('http://example.com/cb'), + ); + + url + .format(redirectUri) + .should.equal('http://example.com/cb#access_token=foobar-token'); + }); + + it('should return the new redirect uri and append `access_token` and `state` in the query', () => { + const responseType = new TokenResponseType({ + accessTokenLifetime: 120, + model: {}, + }); + + responseType.accessToken = 'foobar-token'; + const redirectUri = responseType.buildRedirectUri( + url.parse('http://example.com/cb?foo=bar', true), + ); + + url + .format(redirectUri) + .should.equal( + 'http://example.com/cb?foo=bar#access_token=foobar-token', + ); + }); + }); +}); diff --git a/test/integration/response.spec.ts b/test/integration/response.spec.ts new file mode 100755 index 000000000..dc73475af --- /dev/null +++ b/test/integration/response.spec.ts @@ -0,0 +1,75 @@ +import { Response } from '../../lib/response'; + +/** + * Test `Response` integration. + */ + +describe('Response integration', () => { + describe('constructor()', () => { + it('should set the `body`', () => { + const response = new Response({ body: 'foo', headers: {} }); + + response.body.should.equal('foo'); + }); + + it('should set the `headers`', () => { + const response = new Response({ + body: {}, + headers: { foo: 'bar', QuX: 'biz' }, + }); + + response.headers.should.eql({ foo: 'bar', qux: 'biz' }); + }); + + it('should set the `status` to 200', () => { + const response = new Response({ body: {}, headers: {} }); + + response.status.should.equal(200); + }); + }); + + describe('get()', () => { + it('should return `undefined` if the field does not exist', () => { + const response = new Response({ body: {}, headers: {} }); + + (response.get('content-type') === undefined).should.be.true(); + }); + + it('should return the value if the field exists', () => { + const response = new Response({ + body: {}, + headers: { 'content-type': 'text/html; charset=utf-8' }, + }); + + response.get('Content-Type').should.equal('text/html; charset=utf-8'); + }); + }); + + describe('redirect()', () => { + it('should set the location header to `url`', () => { + const response = new Response({ body: {}, headers: {} }); + + response.redirect('http://example.com'); + + response.get('Location').should.equal('http://example.com'); + }); + + it('should set the `status` to 302', () => { + const response = new Response({ body: {}, headers: {} }); + + response.redirect('http://example.com'); + + response.status.should.equal(302); + }); + }); + + describe('set()', () => { + it('should set the `field`', () => { + const response = new Response({ body: {}, headers: {} }); + + response.set('foo', 'bar'); + + response.headers.should.eql({ foo: 'bar' }); + }); + }); +}); diff --git a/test/integration/response_test.js b/test/integration/response_test.js deleted file mode 100644 index 1e1e0206a..000000000 --- a/test/integration/response_test.js +++ /dev/null @@ -1,75 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var Response = require('../../lib/response'); - -/** - * Test `Response` integration. - */ - -describe('Response integration', function() { - describe('constructor()', function() { - it('should set the `body`', function() { - var response = new Response({ body: 'foo', headers: {} }); - - response.body.should.equal('foo'); - }); - - it('should set the `headers`', function() { - var response = new Response({ body: {}, headers: { foo: 'bar', QuX: 'biz' } }); - - response.headers.should.eql({ foo: 'bar', qux: 'biz' }); - }); - - it('should set the `status` to 200', function() { - var response = new Response({ body: {}, headers: {} }); - - response.status.should.equal(200); - }); - }); - - describe('get()', function() { - it('should return `undefined` if the field does not exist', function() { - var response = new Response({ body: {}, headers: {} }); - - (undefined === response.get('content-type')).should.be.true; - }); - - it('should return the value if the field exists', function() { - var response = new Response({ body: {}, headers: { 'content-type': 'text/html; charset=utf-8' } }); - - response.get('Content-Type').should.equal('text/html; charset=utf-8'); - }); - }); - - describe('redirect()', function() { - it('should set the location header to `url`', function() { - var response = new Response({ body: {}, headers: {} }); - - response.redirect('http://example.com'); - - response.get('Location').should.equal('http://example.com'); - }); - - it('should set the `status` to 302', function() { - var response = new Response({ body: {}, headers: {} }); - - response.redirect('http://example.com'); - - response.status.should.equal(302); - }); - }); - - describe('set()', function() { - it('should set the `field`', function() { - var response = new Response({ body: {}, headers: {} }); - - response.set('foo', 'bar'); - - response.headers.should.eql({ foo: 'bar' }); - }); - }); -}); diff --git a/test/integration/server.spec.ts b/test/integration/server.spec.ts new file mode 100755 index 000000000..cda191909 --- /dev/null +++ b/test/integration/server.spec.ts @@ -0,0 +1,354 @@ +import * as should from 'should'; +import * as sinon from 'sinon'; +import { InvalidArgumentError } from '../../lib/errors'; +import { + AuthenticateHandler, + AuthorizeHandler, + TokenHandler, +} from '../../lib/handlers'; +import { Request } from '../../lib/request'; +import { Response } from '../../lib/response'; +import { OAuth2Server as Server } from '../../lib/server'; + +/** + * Test `Server` integration. + */ + +describe('Server integration', () => { + describe('constructor()', () => { + it('should throw an error if `model` is missing', () => { + try { + new Server({}); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `model`'); + } + }); + + it('should set the `model`', () => { + const model = {}; + const server = new Server({ model }); + + server.options.model.should.equal(model); + }); + }); + + describe('authenticate()', () => { + it('should set the default `options`', async () => { + const model = { + getAccessToken() { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + }; + const server = new Server({ model }); + const request = new Request({ + body: {}, + headers: { Authorization: 'Bearer foo' }, + method: 'ANY', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + try { + const stub = sinon + .stub(AuthenticateHandler.prototype, 'handle') + .returnsThis(); + const token = await server.authenticate(request, response); + token.addAcceptedScopesHeader.should.be.true(); + token.addAuthorizedScopesHeader.should.be.true(); + token.allowBearerTokensInQueryString.should.be.false(); + stub.restore(); + } catch (error) { + should.fail('should.fail', ''); + } + }); + + it('should return a promise', () => { + const model = { + async getAccessToken(token) { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + }; + const server = new Server({ model }); + const request = new Request({ + body: {}, + headers: { Authorization: 'Bearer foo' }, + method: 'ANY', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + const handler = server.authenticate(request, response); + + handler.should.be.an.instanceOf(Promise); + }); + + /* it('should support callbacks', next => { + const model = { + getAccessToken() { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + }; + const server = new Server({ model }); + const request = new Request({ + body: {}, + headers: { Authorization: 'Bearer foo' }, + method: 'ANY', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + // server.authenticate(request, response, null, next); + }); */ + }); + + describe('authorize()', () => { + it('should set the default `options`', async () => { + const model = { + async getAccessToken() { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + async getClient() { + return { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + }, + async saveAuthorizationCode() { + return { authorizationCode: 123 }; + }, + }; + const server = new Server({ model }); + const request = new Request({ + body: { + client_id: 1234, + client_secret: 'secret', + response_type: 'code', + }, + headers: { Authorization: 'Bearer foo' }, + method: 'ANY', + query: { state: 'foobar' }, + }); + const response = new Response({ body: {}, headers: {} }); + // try { + const stub = sinon + .stub(AuthorizeHandler.prototype, 'handle') + .returnsThis(); + const code = await server.authorize(request, response); + const options = code.options; + options.allowEmptyState.should.be.false(); + options.authorizationCodeLifetime.should.be.equal(300); + stub.restore(); + // } catch (error) { + // should.fail('should.fail', ''); + // } + }); + + it('should return a promise', () => { + const model = { + getAccessToken() { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + getClient() { + return { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + }, + saveAuthorizationCode() { + return { authorizationCode: 123 }; + }, + }; + const server = new Server({ model }); + const request = new Request({ + body: { + client_id: 1234, + client_secret: 'secret', + response_type: 'code', + }, + headers: { Authorization: 'Bearer foo' }, + method: 'ANY', + query: { state: 'foobar' }, + }); + + const response = new Response({ body: {}, headers: {} }); + try { + const handler = server.authorize(request, response); + handler.should.be.an.instanceOf(Promise); + } catch (error) { + should.fail('should.fail', ''); + } + }); + + /* it('should support callbacks', next => { + const model = { + getAccessToken() { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + }; + }, + getClient() { + return { + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + }, + saveAuthorizationCode() { + return { authorizationCode: 123 }; + }, + }; + const server = new Server({ model }); + const request = new Request({ + body: { + client_id: 1234, + client_secret: 'secret', + response_type: 'code', + }, + headers: { Authorization: 'Bearer foo' }, + method: 'ANY', + query: { state: 'foobar' }, + }); + const response = new Response({ body: {}, headers: {} }); + + // tslint:disable-next-line: no-floating-promises + // server.authorize(request, response, undefined, next); + }); */ + }); + + describe('token()', () => { + it('should set the default `options`', async () => { + const model = { + async getClient() { + return { grants: ['password'] }; + }, + async getUser() { + return {}; + }, + async saveToken() { + return { accessToken: 1234, client: {}, user: {} }; + }, + async validateScope() { + return 'foo'; + }, + }; + const server = new Server({ model }); + const request = new Request({ + body: { + client_id: 1234, + client_secret: 'secret', + grant_type: 'password', + username: 'foo', + password: 'pass', + scope: 'foo', + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + const stub = sinon.stub(TokenHandler.prototype, 'handle').returnsThis(); + // try { + const token = await server.token(request, response); + token.accessTokenLifetime.should.equal(3600); + token.refreshTokenLifetime.should.equal(1209600); + stub.restore(); + // } catch (error) { + // should.fail('should.fail', ''); + // } + }); + + it('should return a promise', () => { + const model = { + async getClient() { + return { grants: ['password'] }; + }, + async getUser() { + return {}; + }, + async saveToken() { + return { accessToken: 1234, client: {}, user: {} }; + }, + }; + const server = new Server({ model }); + const request = new Request({ + body: { + client_id: 1234, + client_secret: 'secret', + grant_type: 'password', + username: 'foo', + password: 'pass', + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + // try { + const handler = server.token(request, response); + + handler.should.be.an.instanceOf(Promise); + // } catch (error) { + // should.fail('should.fail', ''); + // } + }); + + /* it('should support callbacks', next => { + const model = { + async () { + return { grants: ['password'] }; + }, + getUser() { + return {}; + }, + saveToken() { + return { accessToken: 1234, client: {}, user: {} }; + }, + validateScope() { + return 'foo'; + }, + }; + const server = new Server({ model }); + const request = new Request({ + body: { + client_id: 1234, + client_secret: 'secret', + grant_type: 'password', + username: 'foo', + password: 'pass', + scope: 'foo', + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked', + }, + method: 'POST', + query: {}, + }); + const response = new Response({ body: {}, headers: {} }); + + // server.token(request, response, null, next); + }); */ + }); +}); diff --git a/test/integration/server_test.js b/test/integration/server_test.js deleted file mode 100644 index 2d3aa7845..000000000 --- a/test/integration/server_test.js +++ /dev/null @@ -1,238 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var InvalidArgumentError = require('../../lib/errors/invalid-argument-error'); -var Promise = require('bluebird'); -var Request = require('../../lib/request'); -var Response = require('../../lib/response'); -var Server = require('../../lib/server'); -var should = require('should'); - -/** - * Test `Server` integration. - */ - -describe('Server integration', function() { - describe('constructor()', function() { - it('should throw an error if `model` is missing', function() { - try { - new Server({}); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `model`'); - } - }); - - it('should set the `model`', function() { - var model = {}; - var server = new Server({ model: model }); - - server.options.model.should.equal(model); - }); - }); - - describe('authenticate()', function() { - it('should set the default `options`', function() { - var model = { - getAccessToken: function() { - return { - user: {}, - accessTokenExpiresAt: new Date(new Date().getTime() + 10000) - }; - } - }; - var server = new Server({ model: model }); - var request = new Request({ body: {}, headers: { 'Authorization': 'Bearer foo' }, method: {}, query: {} }); - var response = new Response({ body: {}, headers: {} }); - - return server.authenticate(request, response) - .then(function() { - this.addAcceptedScopesHeader.should.be.true; - this.addAuthorizedScopesHeader.should.be.true; - this.allowBearerTokensInQueryString.should.be.false; - }) - .catch(should.fail); - }); - - it('should return a promise', function() { - var model = { - getAccessToken: function(token, callback) { - callback(null, { - user: {}, - accessTokenExpiresAt: new Date(new Date().getTime() + 10000) - }); - } - }; - var server = new Server({ model: model }); - var request = new Request({ body: {}, headers: { 'Authorization': 'Bearer foo' }, method: {}, query: {} }); - var response = new Response({ body: {}, headers: {} }); - var handler = server.authenticate(request, response); - - handler.should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function(next) { - var model = { - getAccessToken: function() { - return { - user: {}, - accessTokenExpiresAt: new Date(new Date().getTime() + 10000) - }; - } - }; - var server = new Server({ model: model }); - var request = new Request({ body: {}, headers: { 'Authorization': 'Bearer foo' }, method: {}, query: {} }); - var response = new Response({ body: {}, headers: {} }); - - server.authenticate(request, response, null, next); - }); - }); - - describe('authorize()', function() { - it('should set the default `options`', function() { - var model = { - getAccessToken: function() { - return { - user: {}, - accessTokenExpiresAt: new Date(new Date().getTime() + 10000) - }; - }, - getClient: function() { - return { grants: ['authorization_code'], redirectUris: ['http://example.com/cb'] }; - }, - saveAuthorizationCode: function() { - return { authorizationCode: 123 }; - } - }; - var server = new Server({ model: model }); - var request = new Request({ body: { client_id: 1234, client_secret: 'secret', response_type: 'code' }, headers: { 'Authorization': 'Bearer foo' }, method: {}, query: { state: 'foobar' } }); - var response = new Response({ body: {}, headers: {} }); - - return server.authorize(request, response) - .then(function() { - this.allowEmptyState.should.be.false; - this.authorizationCodeLifetime.should.equal(300); - }) - .catch(should.fail); - }); - - it('should return a promise', function() { - var model = { - getAccessToken: function() { - return { - user: {}, - accessTokenExpiresAt: new Date(new Date().getTime() + 10000) - }; - }, - getClient: function() { - return { grants: ['authorization_code'], redirectUris: ['http://example.com/cb'] }; - }, - saveAuthorizationCode: function() { - return { authorizationCode: 123 }; - } - }; - var server = new Server({ model: model }); - var request = new Request({ body: { client_id: 1234, client_secret: 'secret', response_type: 'code' }, headers: { 'Authorization': 'Bearer foo' }, method: {}, query: { state: 'foobar' } }); - var response = new Response({ body: {}, headers: {} }); - var handler = server.authorize(request, response); - - handler.should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function(next) { - var model = { - getAccessToken: function() { - return { - user: {}, - accessTokenExpiresAt: new Date(new Date().getTime() + 10000) - }; - }, - getClient: function() { - return { grants: ['authorization_code'], redirectUris: ['http://example.com/cb'] }; - }, - saveAuthorizationCode: function() { - return { authorizationCode: 123 }; - } - }; - var server = new Server({ model: model }); - var request = new Request({ body: { client_id: 1234, client_secret: 'secret', response_type: 'code' }, headers: { 'Authorization': 'Bearer foo' }, method: {}, query: { state: 'foobar' } }); - var response = new Response({ body: {}, headers: {} }); - - server.authorize(request, response, null, next); - }); - }); - - describe('token()', function() { - it('should set the default `options`', function() { - var model = { - getClient: function() { - return { grants: ['password'] }; - }, - getUser: function() { - return {}; - }, - saveToken: function() { - return { accessToken: 1234, client: {}, user: {} }; - }, - validateScope: function() { return 'foo'; } - }; - var server = new Server({ model: model }); - var request = new Request({ body: { client_id: 1234, client_secret: 'secret', grant_type: 'password', username: 'foo', password: 'pass', scope: 'foo' }, headers: { 'content-type': 'application/x-www-form-urlencoded', 'transfer-encoding': 'chunked' }, method: 'POST', query: {} }); - var response = new Response({ body: {}, headers: {} }); - - return server.token(request, response) - .then(function() { - this.accessTokenLifetime.should.equal(3600); - this.refreshTokenLifetime.should.equal(1209600); - }) - .catch(should.fail); - }); - - it('should return a promise', function() { - var model = { - getClient: function() { - return { grants: ['password'] }; - }, - getUser: function() { - return {}; - }, - saveToken: function() { - return { accessToken: 1234, client: {}, user: {} }; - } - }; - var server = new Server({ model: model }); - var request = new Request({ body: { client_id: 1234, client_secret: 'secret', grant_type: 'password', username: 'foo', password: 'pass' }, headers: { 'content-type': 'application/x-www-form-urlencoded', 'transfer-encoding': 'chunked' }, method: 'POST', query: {} }); - var response = new Response({ body: {}, headers: {} }); - var handler = server.token(request, response); - - handler.should.be.an.instanceOf(Promise); - }); - - it('should support callbacks', function(next) { - var model = { - getClient: function() { - return { grants: ['password'] }; - }, - getUser: function() { - return {}; - }, - saveToken: function() { - return { accessToken: 1234, client: {}, user: {} }; - }, - validateScope: function() { - return 'foo'; - } - }; - var server = new Server({ model: model }); - var request = new Request({ body: { client_id: 1234, client_secret: 'secret', grant_type: 'password', username: 'foo', password: 'pass', scope: 'foo' }, headers: { 'content-type': 'application/x-www-form-urlencoded', 'transfer-encoding': 'chunked' }, method: 'POST', query: {} }); - var response = new Response({ body: {}, headers: {} }); - - server.token(request, response, null, next); - }); - }); -}); diff --git a/test/integration/token-types/bearer-token-type.spec.ts b/test/integration/token-types/bearer-token-type.spec.ts new file mode 100755 index 000000000..44ef8ef11 --- /dev/null +++ b/test/integration/token-types/bearer-token-type.spec.ts @@ -0,0 +1,135 @@ +import * as should from 'should'; +import { InvalidArgumentError } from '../../../lib/errors'; +import { BearerTokenType } from '../../../lib/token-types'; + +/** + * Test `BearerTokenType` integration. + */ + +describe('BearerTokenType integration', () => { + describe('constructor()', () => { + it('should throw an error if `accessToken` is missing', () => { + try { + new BearerTokenType( + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + should.fail('should.fail', ''); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Missing parameter: `accessToken`'); + } + }); + + it('should set the `accessToken`', () => { + const responseType = new BearerTokenType( + 'foo', + 'bar' as any, + undefined, + undefined, + undefined, + ); + + responseType.accessToken.should.equal('foo'); + }); + + it('should set the `accessTokenLifetime`', () => { + const responseType = new BearerTokenType( + 'foo', + 'bar' as any, + undefined, + undefined, + undefined, + ); + + responseType.accessTokenLifetime.should.equal('bar'); + }); + + it('should set the `refreshToken`', () => { + const responseType = new BearerTokenType( + 'foo', + 'bar' as any, + 'biz', + undefined, + undefined, + ); + + responseType.refreshToken.should.equal('biz'); + }); + }); + + describe('valueOf()', () => { + it('should return the value representation', () => { + const responseType = new BearerTokenType( + 'foo', + 'bar' as any, + undefined, + undefined, + undefined, + ); + const value = responseType.valueOf(); + + value.should.eql({ + access_token: 'foo', + expires_in: 'bar', + token_type: 'Bearer', + }); + }); + + it('should not include the `expires_in` if not given', () => { + const responseType = new BearerTokenType( + 'foo', + undefined, + undefined, + undefined, + undefined, + ); + const value = responseType.valueOf(); + + value.should.eql({ + access_token: 'foo', + token_type: 'Bearer', + }); + }); + + it('should set `refresh_token` if `refreshToken` is defined', () => { + const responseType = new BearerTokenType( + 'foo', + 'bar' as any, + 'biz', + undefined, + undefined, + ); + const value = responseType.valueOf(); + + value.should.eql({ + access_token: 'foo', + expires_in: 'bar', + refresh_token: 'biz', + token_type: 'Bearer', + }); + }); + + it('should set `expires_in` if `accessTokenLifetime` is defined', () => { + const responseType = new BearerTokenType( + 'foo', + 'bar' as any, + 'biz', + undefined, + undefined, + ); + const value = responseType.valueOf(); + + value.should.eql({ + access_token: 'foo', + expires_in: 'bar', + refresh_token: 'biz', + token_type: 'Bearer', + }); + }); + }); +}); diff --git a/test/integration/token-types/bearer-token-type_test.js b/test/integration/token-types/bearer-token-type_test.js deleted file mode 100644 index 3c1ef6bdd..000000000 --- a/test/integration/token-types/bearer-token-type_test.js +++ /dev/null @@ -1,93 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var BearerTokenType = require('../../../lib/token-types/bearer-token-type'); -var InvalidArgumentError = require('../../../lib/errors/invalid-argument-error'); -var should = require('should'); - -/** - * Test `BearerTokenType` integration. - */ - -describe('BearerTokenType integration', function() { - describe('constructor()', function() { - it('should throw an error if `accessToken` is missing', function() { - try { - new BearerTokenType(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `accessToken`'); - } - }); - - it('should set the `accessToken`', function() { - var responseType = new BearerTokenType('foo', 'bar'); - - responseType.accessToken.should.equal('foo'); - }); - - it('should set the `accessTokenLifetime`', function() { - var responseType = new BearerTokenType('foo', 'bar'); - - responseType.accessTokenLifetime.should.equal('bar'); - }); - - it('should set the `refreshToken`', function() { - var responseType = new BearerTokenType('foo', 'bar', 'biz'); - - responseType.refreshToken.should.equal('biz'); - }); - }); - - describe('valueOf()', function() { - it('should return the value representation', function() { - var responseType = new BearerTokenType('foo', 'bar'); - var value = responseType.valueOf(); - - value.should.eql({ - access_token: 'foo', - expires_in: 'bar', - token_type: 'Bearer' - }); - }); - - it('should not include the `expires_in` if not given', function() { - var responseType = new BearerTokenType('foo'); - var value = responseType.valueOf(); - - value.should.eql({ - access_token: 'foo', - token_type: 'Bearer' - }); - }); - - it('should set `refresh_token` if `refreshToken` is defined', function() { - var responseType = new BearerTokenType('foo', 'bar', 'biz'); - var value = responseType.valueOf(); - - value.should.eql({ - access_token: 'foo', - expires_in: 'bar', - refresh_token: 'biz', - token_type: 'Bearer' - }); - }); - - it('should set `expires_in` if `accessTokenLifetime` is defined', function() { - var responseType = new BearerTokenType('foo', 'bar', 'biz'); - var value = responseType.valueOf(); - - value.should.eql({ - access_token: 'foo', - expires_in: 'bar', - refresh_token: 'biz', - token_type: 'Bearer' - }); - }); - }); -}); diff --git a/test/integration/utils/token-util.spec.ts b/test/integration/utils/token-util.spec.ts new file mode 100755 index 000000000..9a7ea1747 --- /dev/null +++ b/test/integration/utils/token-util.spec.ts @@ -0,0 +1,19 @@ +import * as should from 'should'; +import * as TokenUtil from '../../../lib/utils/token-util'; + +/** + * Test `TokenUtil` integration. + */ + +describe('TokenUtil integration', () => { + describe('generateRandomToken()', () => { + it('should return a sha-1 token', async () => { + try { + const token: any = await TokenUtil.GenerateRandomToken(); + token.should.be.a.sha1(); + } catch (error) { + should.fail('should.fail', ''); + } + }); + }); +}); diff --git a/test/integration/utils/token-util_test.js b/test/integration/utils/token-util_test.js deleted file mode 100644 index 3fbca3f65..000000000 --- a/test/integration/utils/token-util_test.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var TokenUtil = require('../../../lib/utils/token-util'); -var should = require('should'); - -/** - * Test `TokenUtil` integration. - */ - -describe('TokenUtil integration', function() { - describe('generateRandomToken()', function() { - it('should return a sha-1 token', function() { - return TokenUtil.generateRandomToken() - .then(function(token) { - token.should.be.a.sha1; - }) - .catch(should.fail); - }); - }); -}); diff --git a/test/mocha.opts b/test/mocha.opts deleted file mode 100644 index 00ecb38eb..000000000 --- a/test/mocha.opts +++ /dev/null @@ -1,4 +0,0 @@ ---require should ---require test/assertions ---ui bdd ---reporter spec diff --git a/test/tslint.json b/test/tslint.json new file mode 100755 index 000000000..8c62b6de9 --- /dev/null +++ b/test/tslint.json @@ -0,0 +1,28 @@ +{ + "extends": ["tslint:recommended", "tslint:all", "tslint:latest"], + "jsRules": { + "no-unused-expression": true + }, + "rules": { + "file-name-casing": [true, "kebab-case"], + "no-unused-expression": false, + "semicolon": [true, "always", "ignore-bound-class-methods"], + "completed-docs": false, + "quotemark": [true, "single"], + "max-file-line-count": [false], + "member-access": [false], + "no-unsafe-any": false, + "promise-function-async": false, + "ordered-imports": [false], + "no-empty": [false], + "newline-per-chained-call": [false], + "max-line-length": [true, 120], + "no-magic-numbers": [false], + "member-ordering": [false], + "interface-name": [false], + "arrow-parens": false, + "no-implicit-dependencies": false, + "object-literal-sort-keys": false + }, + "rulesDirectory": [] +} diff --git a/test/unit/grant-types/abstract-grant-type.spec.ts b/test/unit/grant-types/abstract-grant-type.spec.ts new file mode 100755 index 000000000..12ab50977 --- /dev/null +++ b/test/unit/grant-types/abstract-grant-type.spec.ts @@ -0,0 +1,53 @@ +import * as should from 'should'; +import * as sinon from 'sinon'; +import { AbstractGrantType } from '../../../lib/grant-types'; + +/** + * Test `AbstractGrantType`. + */ + +describe('AbstractGrantType', () => { + describe('generateAccessToken()', () => { + it('should call `model.generateAccessToken()`', async () => { + const model = { + generateAccessToken: sinon + .stub() + .returns({ client: {}, expiresAt: new Date(), user: {} }), + }; + const handler = new AbstractGrantType({ + accessTokenLifetime: 120, + model, + }); + try { + await handler.generateAccessToken(); + model.generateAccessToken.callCount.should.equal(1); + model.generateAccessToken.firstCall.thisValue.should.equal(model); + } catch (error) { + should.fail('should.fail', ''); + } + }); + }); + + describe('generateRefreshToken()', () => { + it('should call `model.generateRefreshToken()`', async () => { + const model = { + generateRefreshToken: sinon.stub().returns({ + client: {}, + expiresAt: new Date(new Date().getTime() / 2), + user: {}, + }), + }; + const handler = new AbstractGrantType({ + accessTokenLifetime: 120, + model, + }); + try { + await handler.generateRefreshToken(); + model.generateRefreshToken.callCount.should.equal(1); + model.generateRefreshToken.firstCall.thisValue.should.equal(model); + } catch (error) { + should.fail('should.fail', ''); + } + }); + }); +}); diff --git a/test/unit/grant-types/abstract-grant-type_test.js b/test/unit/grant-types/abstract-grant-type_test.js deleted file mode 100644 index 528ca4041..000000000 --- a/test/unit/grant-types/abstract-grant-type_test.js +++ /dev/null @@ -1,47 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var AbstractGrantType = require('../../../lib/grant-types/abstract-grant-type'); -var sinon = require('sinon'); -var should = require('should'); - -/** - * Test `AbstractGrantType`. - */ - -describe('AbstractGrantType', function() { - describe('generateAccessToken()', function() { - it('should call `model.generateAccessToken()`', function() { - var model = { - generateAccessToken: sinon.stub().returns({ client: {}, expiresAt: new Date(), user: {} }) - }; - var handler = new AbstractGrantType({ accessTokenLifetime: 120, model: model }); - - return handler.generateAccessToken() - .then(function() { - model.generateAccessToken.callCount.should.equal(1); - model.generateAccessToken.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); - - describe('generateRefreshToken()', function() { - it('should call `model.generateRefreshToken()`', function() { - var model = { - generateRefreshToken: sinon.stub().returns({ client: {}, expiresAt: new Date(new Date() / 2), user: {} }) - }; - var handler = new AbstractGrantType({ accessTokenLifetime: 120, model: model }); - - return handler.generateRefreshToken() - .then(function() { - model.generateRefreshToken.callCount.should.equal(1); - model.generateRefreshToken.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); -}); diff --git a/test/unit/grant-types/authorization-code-grant-type.spec.ts b/test/unit/grant-types/authorization-code-grant-type.spec.ts new file mode 100755 index 000000000..9ff43eaa0 --- /dev/null +++ b/test/unit/grant-types/authorization-code-grant-type.spec.ts @@ -0,0 +1,117 @@ +import * as should from 'should'; +import * as sinon from 'sinon'; +import { AuthorizationCodeGrantType } from '../../../lib/grant-types'; +import { Request } from '../../../lib/request'; + +/** + * Test `AuthorizationCodeGrantType`. + */ + +describe('AuthorizationCodeGrantType', () => { + describe('getAuthorizationCode()', () => { + it('should call `model.getAuthorizationCode()`', async () => { + const model = { + getAuthorizationCode: sinon.stub().returns({ + authorizationCode: 12345, + client: {}, + expiresAt: new Date(new Date().getTime() * 2), + user: {}, + }), + revokeAuthorizationCode() {}, + saveToken() {}, + }; + const handler = new AuthorizationCodeGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: { code: 12345 }, + headers: {}, + method: 'ANY', + query: {}, + }); + const client: any = {}; + try { + await handler.getAuthorizationCode(request, client); + + model.getAuthorizationCode.callCount.should.equal(1); + model.getAuthorizationCode.firstCall.args.should.have.length(1); + model.getAuthorizationCode.firstCall.args[0].should.equal(12345); + model.getAuthorizationCode.firstCall.thisValue.should.equal(model); + } catch (error) { + should.fail('should.fail', ''); + } + }); + }); + + describe('revokeAuthorizationCode()', () => { + it('should call `model.revokeAuthorizationCode()`', async () => { + const model = { + getAuthorizationCode() {}, + revokeAuthorizationCode: sinon.stub().returns(true), + saveToken() {}, + }; + const handler = new AuthorizationCodeGrantType({ + accessTokenLifetime: 120, + model, + }); + const authorizationCode: any = {}; + try { + await handler.revokeAuthorizationCode(authorizationCode); + + model.revokeAuthorizationCode.callCount.should.equal(1); + model.revokeAuthorizationCode.firstCall.args.should.have.length(1); + model.revokeAuthorizationCode.firstCall.args[0].should.equal( + authorizationCode, + ); + model.revokeAuthorizationCode.firstCall.thisValue.should.equal(model); + } catch (error) { + should.fail('should.fail', ''); + } + }); + }); + + describe('saveToken()', () => { + it('should call `model.saveToken()`', async () => { + const client: any = {}; + const user = {}; + const model = { + getAuthorizationCode() {}, + revokeAuthorizationCode() {}, + saveToken: sinon.stub().returns(true), + }; + const handler = new AuthorizationCodeGrantType({ + accessTokenLifetime: 120, + model, + }); + + sinon.stub(handler, 'validateScope').returns('foobiz' as any); + sinon + .stub(handler, 'generateAccessToken') + .returns(Promise.resolve('foo')); + sinon + .stub(handler, 'generateRefreshToken') + .returns(Promise.resolve('bar')); + sinon.stub(handler, 'getAccessTokenExpiresAt').returns('biz' as any); + sinon.stub(handler, 'getRefreshTokenExpiresAt').returns('baz' as any); + try { + await handler.saveToken(user, client, 'foobar', 'foobiz'); + model.saveToken.callCount.should.equal(1); + model.saveToken.firstCall.args.should.have.length(3); + model.saveToken.firstCall.args[0].should.eql({ + accessToken: 'foo', + authorizationCode: 'foobar', + accessTokenExpiresAt: 'biz', + refreshToken: 'bar', + refreshTokenExpiresAt: 'baz', + scope: 'foobiz', + }); + model.saveToken.firstCall.args[1].should.equal(client); + model.saveToken.firstCall.args[2].should.equal(user); + model.saveToken.firstCall.thisValue.should.equal(model); + } catch (error) { + should.fail('should.fail', ''); + } + }); + }); +}); diff --git a/test/unit/grant-types/authorization-code-grant-type_test.js b/test/unit/grant-types/authorization-code-grant-type_test.js deleted file mode 100644 index 480416e68..000000000 --- a/test/unit/grant-types/authorization-code-grant-type_test.js +++ /dev/null @@ -1,90 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var AuthorizationCodeGrantType = require('../../../lib/grant-types/authorization-code-grant-type'); -var Promise = require('bluebird'); -var Request = require('../../../lib/request'); -var sinon = require('sinon'); -var should = require('should'); - -/** - * Test `AuthorizationCodeGrantType`. - */ - -describe('AuthorizationCodeGrantType', function() { - describe('getAuthorizationCode()', function() { - it('should call `model.getAuthorizationCode()`', function() { - var model = { - getAuthorizationCode: sinon.stub().returns({ authorizationCode: 12345, client: {}, expiresAt: new Date(new Date() * 2), user: {} }), - revokeAuthorizationCode: function() {}, - saveToken: function() {} - }; - var handler = new AuthorizationCodeGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); - var client = {}; - - return handler.getAuthorizationCode(request, client) - .then(function() { - model.getAuthorizationCode.callCount.should.equal(1); - model.getAuthorizationCode.firstCall.args.should.have.length(1); - model.getAuthorizationCode.firstCall.args[0].should.equal(12345); - model.getAuthorizationCode.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); - - describe('revokeAuthorizationCode()', function() { - it('should call `model.revokeAuthorizationCode()`', function() { - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: sinon.stub().returns(true), - saveToken: function() {} - }; - var handler = new AuthorizationCodeGrantType({ accessTokenLifetime: 120, model: model }); - var authorizationCode = {}; - - return handler.revokeAuthorizationCode(authorizationCode) - .then(function() { - model.revokeAuthorizationCode.callCount.should.equal(1); - model.revokeAuthorizationCode.firstCall.args.should.have.length(1); - model.revokeAuthorizationCode.firstCall.args[0].should.equal(authorizationCode); - model.revokeAuthorizationCode.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); - - describe('saveToken()', function() { - it('should call `model.saveToken()`', function() { - var client = {}; - var user = {}; - var model = { - getAuthorizationCode: function() {}, - revokeAuthorizationCode: function() {}, - saveToken: sinon.stub().returns(true) - }; - var handler = new AuthorizationCodeGrantType({ accessTokenLifetime: 120, model: model }); - - sinon.stub(handler, 'validateScope').returns('foobiz'); - sinon.stub(handler, 'generateAccessToken').returns(Promise.resolve('foo')); - sinon.stub(handler, 'generateRefreshToken').returns(Promise.resolve('bar')); - sinon.stub(handler, 'getAccessTokenExpiresAt').returns(Promise.resolve('biz')); - sinon.stub(handler, 'getRefreshTokenExpiresAt').returns(Promise.resolve('baz')); - - return handler.saveToken(user, client, 'foobar', 'foobiz') - .then(function() { - model.saveToken.callCount.should.equal(1); - model.saveToken.firstCall.args.should.have.length(3); - model.saveToken.firstCall.args[0].should.eql({ accessToken: 'foo', authorizationCode: 'foobar', accessTokenExpiresAt: 'biz', refreshToken: 'bar', refreshTokenExpiresAt: 'baz', scope: 'foobiz' }); - model.saveToken.firstCall.args[1].should.equal(client); - model.saveToken.firstCall.args[2].should.equal(user); - model.saveToken.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); -}); diff --git a/test/unit/grant-types/client-credentials-grant-type.spec.ts b/test/unit/grant-types/client-credentials-grant-type.spec.ts new file mode 100755 index 000000000..693c7d669 --- /dev/null +++ b/test/unit/grant-types/client-credentials-grant-type.spec.ts @@ -0,0 +1,66 @@ +import * as should from 'should'; +import * as sinon from 'sinon'; +import { ClientCredentialsGrantType } from '../../../lib/grant-types'; + +/** + * Test `ClientCredentialsGrantType`. + */ + +describe('ClientCredentialsGrantType', () => { + describe('getUserFromClient()', () => { + it('should call `model.getUserFromClient()`', async () => { + const model = { + getUserFromClient: sinon.stub().returns(true), + saveToken() {}, + }; + const handler = new ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + const client: any = {}; + try { + await handler.getUserFromClient(client); + model.getUserFromClient.callCount.should.equal(1); + model.getUserFromClient.firstCall.args.should.have.length(1); + model.getUserFromClient.firstCall.args[0].should.equal(client); + model.getUserFromClient.firstCall.thisValue.should.equal(model); + } catch (error) { + should.fail('should.fail', ''); + } + }); + }); + + describe('saveToken()', () => { + it('should call `model.saveToken()`', async () => { + const client: any = {}; + const user = {}; + const model = { + getUserFromClient() {}, + saveToken: sinon.stub().returns(true), + }; + const handler = new ClientCredentialsGrantType({ + accessTokenLifetime: 120, + model, + }); + + sinon.stub(handler, 'validateScope').returns('foobar' as any); + sinon.stub(handler, 'generateAccessToken').returns('foo' as any); + sinon.stub(handler, 'getAccessTokenExpiresAt').returns('biz' as any); + try { + await handler.saveToken(user, client, 'foobar'); + model.saveToken.callCount.should.equal(1); + model.saveToken.firstCall.args.should.have.length(3); + model.saveToken.firstCall.args[0].should.eql({ + accessToken: 'foo', + accessTokenExpiresAt: 'biz', + scope: 'foobar', + }); + model.saveToken.firstCall.args[1].should.equal(client); + model.saveToken.firstCall.args[2].should.equal(user); + model.saveToken.firstCall.thisValue.should.equal(model); + } catch (error) { + should.fail('should.fail', ''); + } + }); + }); +}); diff --git a/test/unit/grant-types/client-credentials-grant-type_test.js b/test/unit/grant-types/client-credentials-grant-type_test.js deleted file mode 100644 index fe1fc4840..000000000 --- a/test/unit/grant-types/client-credentials-grant-type_test.js +++ /dev/null @@ -1,62 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var ClientCredentialsGrantType = require('../../../lib/grant-types/client-credentials-grant-type'); -var sinon = require('sinon'); -var should = require('should'); - -/** - * Test `ClientCredentialsGrantType`. - */ - -describe('ClientCredentialsGrantType', function() { - describe('getUserFromClient()', function() { - it('should call `model.getUserFromClient()`', function() { - var model = { - getUserFromClient: sinon.stub().returns(true), - saveToken: function() {} - }; - var handler = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); - var client = {}; - - return handler.getUserFromClient(client) - .then(function() { - model.getUserFromClient.callCount.should.equal(1); - model.getUserFromClient.firstCall.args.should.have.length(1); - model.getUserFromClient.firstCall.args[0].should.equal(client); - model.getUserFromClient.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); - - describe('saveToken()', function() { - it('should call `model.saveToken()`', function() { - var client = {}; - var user = {}; - var model = { - getUserFromClient: function() {}, - saveToken: sinon.stub().returns(true) - }; - var handler = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); - - sinon.stub(handler, 'validateScope').returns('foobar'); - sinon.stub(handler, 'generateAccessToken').returns('foo'); - sinon.stub(handler, 'getAccessTokenExpiresAt').returns('biz'); - - return handler.saveToken(user, client, 'foobar') - .then(function() { - model.saveToken.callCount.should.equal(1); - model.saveToken.firstCall.args.should.have.length(3); - model.saveToken.firstCall.args[0].should.eql({ accessToken: 'foo', accessTokenExpiresAt: 'biz', scope: 'foobar' }); - model.saveToken.firstCall.args[1].should.equal(client); - model.saveToken.firstCall.args[2].should.equal(user); - model.saveToken.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); -}); diff --git a/test/unit/grant-types/implict-grant-type.spec.ts b/test/unit/grant-types/implict-grant-type.spec.ts new file mode 100644 index 000000000..504f693e3 --- /dev/null +++ b/test/unit/grant-types/implict-grant-type.spec.ts @@ -0,0 +1,45 @@ +import * as should from 'should'; +import * as sinon from 'sinon'; +import { ImplicitGrantType } from '../../../lib/grant-types'; +/** + * Test `ImplicitGrantType`. + */ + +describe('ImplicitGrantType', () => { + describe('saveToken()', () => { + it('should call `model.saveToken()`', () => { + const client = {}; + const user = {}; + const model = { + saveToken: sinon.stub().returns(true), + }; + const handler: any = new ImplicitGrantType({ + accessTokenLifetime: 120, + model, + user, + }); + + sinon.stub(handler, 'validateScope').returns('foobar-scope'); + sinon + .stub(handler, 'generateAccessToken') + .returns(Promise.resolve('foobar-token')); + sinon.stub(handler, 'getAccessTokenExpiresAt').returns('foo-1234'); + + return handler + .saveToken(user, client, 'foobar') + .then(() => { + model.saveToken.callCount.should.equal(1); + model.saveToken.firstCall.args.should.have.length(3); + model.saveToken.firstCall.args[0].should.eql({ + accessToken: 'foobar-token', + accessTokenExpiresAt: 'foo-1234', + scope: 'foobar-scope', + }); + model.saveToken.firstCall.args[1].should.equal(client); + model.saveToken.firstCall.args[2].should.equal(user); + model.saveToken.firstCall.thisValue.should.equal(model); + }) + .catch(should.fail); + }); + }); +}); diff --git a/test/unit/grant-types/password-grant-type.spec.ts b/test/unit/grant-types/password-grant-type.spec.ts new file mode 100755 index 000000000..c4f77beff --- /dev/null +++ b/test/unit/grant-types/password-grant-type.spec.ts @@ -0,0 +1,78 @@ +import * as should from 'should'; +import * as sinon from 'sinon'; +import { PasswordGrantType } from '../../../lib/grant-types'; +import { Request } from '../../../lib/request'; + +/** + * Test `PasswordGrantType`. + */ + +describe('PasswordGrantType', () => { + describe('getUser()', () => { + it('should call `model.getUser()`', async () => { + const model = { + getUser: sinon.stub().returns(true), + saveToken() {}, + }; + const handler = new PasswordGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: { username: 'foo', password: 'bar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await handler.getUser(request); + model.getUser.callCount.should.equal(1); + model.getUser.firstCall.args.should.have.length(2); + model.getUser.firstCall.args[0].should.equal('foo'); + model.getUser.firstCall.args[1].should.equal('bar'); + model.getUser.firstCall.thisValue.should.equal(model); + } catch (error) { + should.fail('should.fail', ''); + } + }); + }); + + describe('saveToken()', () => { + it('should call `model.saveToken()`', async () => { + const client: any = {}; + const user = {}; + const model = { + getUser() {}, + saveToken: sinon.stub().returns(true), + }; + const handler = new PasswordGrantType({ + accessTokenLifetime: 120, + model, + }); + + sinon.stub(handler, 'validateScope').returns('foobar' as any); + sinon.stub(handler, 'generateAccessToken').returns('foo' as any); + sinon.stub(handler, 'generateRefreshToken').returns('bar' as any); + sinon.stub(handler, 'getAccessTokenExpiresAt').returns('biz' as any); + sinon.stub(handler, 'getRefreshTokenExpiresAt').returns('baz' as any); + try { + await handler.saveToken(user, client, 'foobar'); + + model.saveToken.callCount.should.equal(1); + model.saveToken.firstCall.args.should.have.length(3); + model.saveToken.firstCall.args[0].should.eql({ + accessToken: 'foo', + accessTokenExpiresAt: 'biz', + refreshToken: 'bar', + refreshTokenExpiresAt: 'baz', + scope: 'foobar', + }); + model.saveToken.firstCall.args[1].should.equal(client); + model.saveToken.firstCall.args[2].should.equal(user); + model.saveToken.firstCall.thisValue.should.equal(model); + } catch (error) { + should.fail('should.fail', ''); + } + }); + }); +}); diff --git a/test/unit/grant-types/password-grant-type_test.js b/test/unit/grant-types/password-grant-type_test.js deleted file mode 100644 index 8e3bfc84e..000000000 --- a/test/unit/grant-types/password-grant-type_test.js +++ /dev/null @@ -1,66 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var PasswordGrantType = require('../../../lib/grant-types/password-grant-type'); -var Request = require('../../../lib/request'); -var sinon = require('sinon'); -var should = require('should'); - -/** - * Test `PasswordGrantType`. - */ - -describe('PasswordGrantType', function() { - describe('getUser()', function() { - it('should call `model.getUser()`', function() { - var model = { - getUser: sinon.stub().returns(true), - saveToken: function() {} - }; - var handler = new PasswordGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: { username: 'foo', password: 'bar' }, headers: {}, method: {}, query: {} }); - - return handler.getUser(request) - .then(function() { - model.getUser.callCount.should.equal(1); - model.getUser.firstCall.args.should.have.length(2); - model.getUser.firstCall.args[0].should.equal('foo'); - model.getUser.firstCall.args[1].should.equal('bar'); - model.getUser.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); - - describe('saveToken()', function() { - it('should call `model.saveToken()`', function() { - var client = {}; - var user = {}; - var model = { - getUser: function() {}, - saveToken: sinon.stub().returns(true) - }; - var handler = new PasswordGrantType({ accessTokenLifetime: 120, model: model }); - - sinon.stub(handler, 'validateScope').returns('foobar'); - sinon.stub(handler, 'generateAccessToken').returns('foo'); - sinon.stub(handler, 'generateRefreshToken').returns('bar'); - sinon.stub(handler, 'getAccessTokenExpiresAt').returns('biz'); - sinon.stub(handler, 'getRefreshTokenExpiresAt').returns('baz'); - - return handler.saveToken(user, client, 'foobar') - .then(function() { - model.saveToken.callCount.should.equal(1); - model.saveToken.firstCall.args.should.have.length(3); - model.saveToken.firstCall.args[0].should.eql({ accessToken: 'foo', accessTokenExpiresAt: 'biz', refreshToken: 'bar', refreshTokenExpiresAt: 'baz', scope: 'foobar' }); - model.saveToken.firstCall.args[1].should.equal(client); - model.saveToken.firstCall.args[2].should.equal(user); - model.saveToken.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); -}); diff --git a/test/unit/grant-types/refresh-token-grant-type.spec.ts b/test/unit/grant-types/refresh-token-grant-type.spec.ts new file mode 100755 index 000000000..6be1ed92c --- /dev/null +++ b/test/unit/grant-types/refresh-token-grant-type.spec.ts @@ -0,0 +1,298 @@ +import * as should from 'should'; +import * as sinon from 'sinon'; +import { RefreshTokenGrantType } from '../../../lib/grant-types'; +import { Request } from '../../../lib/request'; + +/** + * Test `RefreshTokenGrantType`. + */ + +describe('RefreshTokenGrantType', () => { + describe('handle()', () => { + it('should revoke the previous token', () => { + const token = { accessToken: 'foo', client: {}, user: {} }; + const model = { + getRefreshToken() { + return token; + }, + saveToken() { + return { accessToken: 'bar', client: {}, user: {} }; + }, + revokeToken: sinon.stub().returns({ + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date().getTime() / 2), + user: {}, + }), + }; + const handler = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: { refresh_token: 'bar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + const client: any = {}; + + return handler + .handle(request, client) + .then(() => { + model.revokeToken.callCount.should.equal(1); + model.revokeToken.firstCall.args.should.have.length(1); + model.revokeToken.firstCall.args[0].should.equal(token); + model.revokeToken.firstCall.thisValue.should.equal(model); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + + describe('getRefreshToken()', () => { + it('should call `model.getRefreshToken()`', () => { + const model = { + getRefreshToken: sinon + .stub() + .returns({ accessToken: 'foo', client: {}, user: {} }), + saveToken() {}, + revokeToken() {}, + }; + const handler = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const request = new Request({ + body: { refresh_token: 'bar' }, + headers: {}, + method: 'ANY', + query: {}, + }); + const client: any = {}; + + return handler + .getRefreshToken(request, client) + .then(() => { + model.getRefreshToken.callCount.should.equal(1); + model.getRefreshToken.firstCall.args.should.have.length(1); + model.getRefreshToken.firstCall.args[0].should.equal('bar'); + model.getRefreshToken.firstCall.thisValue.should.equal(model); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + + describe('revokeToken()', () => { + it('should call `model.revokeToken()`', () => { + const model = { + getRefreshToken() {}, + revokeToken: sinon.stub().returns({ + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date().getTime() / 2), + user: {}, + }), + saveToken() {}, + }; + const handler = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + const token: any = {}; + + return handler + .revokeToken(token) + .then(() => { + model.revokeToken.callCount.should.equal(1); + model.revokeToken.firstCall.args.should.have.length(1); + model.revokeToken.firstCall.args[0].should.equal(token); + model.revokeToken.firstCall.thisValue.should.equal(model); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + + it('should not call `model.revokeToken()`', () => { + const model = { + getRefreshToken() {}, + revokeToken: sinon.stub().returns({ + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date().getTime() / 2), + user: {}, + }), + saveToken() {}, + }; + const handler = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + alwaysIssueNewRefreshToken: false, + }); + const token: any = {}; + + return handler + .revokeToken(token) + .then(() => { + model.revokeToken.callCount.should.equal(0); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + + it('should not call `model.revokeToken()`', () => { + const model = { + getRefreshToken() {}, + revokeToken: sinon.stub().returns({ + accessToken: 'foo', + client: {}, + refreshTokenExpiresAt: new Date(new Date().getTime() / 2), + user: {}, + }), + saveToken() {}, + }; + const handler = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + alwaysIssueNewRefreshToken: true, + }); + const token: any = {}; + + return handler + .revokeToken(token) + .then(() => { + model.revokeToken.callCount.should.equal(1); + model.revokeToken.firstCall.args.should.have.length(1); + model.revokeToken.firstCall.args[0].should.equal(token); + model.revokeToken.firstCall.thisValue.should.equal(model); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + + describe('saveToken()', () => { + it('should call `model.saveToken()`', () => { + const client: any = {}; + const user = {}; + const model = { + getRefreshToken() {}, + revokeToken() {}, + saveToken: sinon.stub().returns(true), + }; + const handler: any = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + }); + + sinon.stub(handler, 'generateAccessToken').returns('foo'); + sinon.stub(handler, 'generateRefreshToken').returns('bar'); + sinon.stub(handler, 'getAccessTokenExpiresAt').returns('biz'); + sinon.stub(handler, 'getRefreshTokenExpiresAt').returns('baz'); + + return handler + .saveToken(user, client, 'foobar') + .then(() => { + model.saveToken.callCount.should.equal(1); + model.saveToken.firstCall.args.should.have.length(3); + model.saveToken.firstCall.args[0].should.eql({ + accessToken: 'foo', + accessTokenExpiresAt: 'biz', + refreshToken: 'bar', + refreshTokenExpiresAt: 'baz', + scope: 'foobar', + }); + model.saveToken.firstCall.args[1].should.equal(client); + model.saveToken.firstCall.args[2].should.equal(user); + model.saveToken.firstCall.thisValue.should.equal(model); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + + it('should call `model.saveToken()` without refresh token', () => { + const client = {}; + const user = {}; + const model = { + getRefreshToken() {}, + revokeToken() {}, + saveToken: sinon.stub().returns(true), + }; + const handler: any = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + alwaysIssueNewRefreshToken: false, + }); + + sinon.stub(handler, 'generateAccessToken').returns('foo' as any); + sinon.stub(handler, 'generateRefreshToken').returns('bar' as any); + sinon.stub(handler, 'getAccessTokenExpiresAt').returns('biz' as any); + sinon.stub(handler, 'getRefreshTokenExpiresAt').returns('baz' as any); + + return handler + .saveToken(user, client, 'foobar') + .then(() => { + model.saveToken.callCount.should.equal(1); + model.saveToken.firstCall.args.should.have.length(3); + model.saveToken.firstCall.args[0].should.eql({ + accessToken: 'foo', + accessTokenExpiresAt: 'biz', + scope: 'foobar', + }); + model.saveToken.firstCall.args[1].should.equal(client); + model.saveToken.firstCall.args[2].should.equal(user); + model.saveToken.firstCall.thisValue.should.equal(model); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + + it('should call `model.saveToken()` with refresh token', () => { + const client = {}; + const user = {}; + const model = { + getRefreshToken() {}, + revokeToken() {}, + saveToken: sinon.stub().returns(true), + }; + const handler: any = new RefreshTokenGrantType({ + accessTokenLifetime: 120, + model, + alwaysIssueNewRefreshToken: true, + }); + + sinon.stub(handler, 'generateAccessToken').returns('foo' as any); + sinon.stub(handler, 'generateRefreshToken').returns('bar' as any); + sinon.stub(handler, 'getAccessTokenExpiresAt').returns('biz' as any); + sinon.stub(handler, 'getRefreshTokenExpiresAt').returns('baz' as any); + + return handler + .saveToken(user, client, 'foobar') + .then(() => { + model.saveToken.callCount.should.equal(1); + model.saveToken.firstCall.args.should.have.length(3); + model.saveToken.firstCall.args[0].should.eql({ + accessToken: 'foo', + accessTokenExpiresAt: 'biz', + refreshToken: 'bar', + refreshTokenExpiresAt: 'baz', + scope: 'foobar', + }); + model.saveToken.firstCall.args[1].should.equal(client); + model.saveToken.firstCall.args[2].should.equal(user); + model.saveToken.firstCall.thisValue.should.equal(model); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); +}); diff --git a/test/unit/grant-types/refresh-token-grant-type_test.js b/test/unit/grant-types/refresh-token-grant-type_test.js deleted file mode 100644 index e5693ba8f..000000000 --- a/test/unit/grant-types/refresh-token-grant-type_test.js +++ /dev/null @@ -1,200 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var RefreshTokenGrantType = require('../../../lib/grant-types/refresh-token-grant-type'); -var Request = require('../../../lib/request'); -var sinon = require('sinon'); -var should = require('should'); - -/** - * Test `RefreshTokenGrantType`. - */ - -describe('RefreshTokenGrantType', function() { - describe('handle()', function() { - it('should revoke the previous token', function() { - var token = { accessToken: 'foo', client: {}, user: {} }; - var model = { - getRefreshToken: function() { return token; }, - saveToken: function() { return { accessToken: 'bar', client: {}, user: {} }; }, - revokeToken: sinon.stub().returns({ accessToken: 'foo', client: {}, refreshTokenExpiresAt: new Date(new Date() / 2), user: {} }) - }; - var handler = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: { refresh_token: 'bar' }, headers: {}, method: {}, query: {} }); - var client = {}; - - return handler.handle(request, client) - .then(function() { - model.revokeToken.callCount.should.equal(1); - model.revokeToken.firstCall.args.should.have.length(1); - model.revokeToken.firstCall.args[0].should.equal(token); - model.revokeToken.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); - - describe('getRefreshToken()', function() { - it('should call `model.getRefreshToken()`', function() { - var model = { - getRefreshToken: sinon.stub().returns({ accessToken: 'foo', client: {}, user: {} }), - saveToken: function() {}, - revokeToken: function() {} - }; - var handler = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - var request = new Request({ body: { refresh_token: 'bar' }, headers: {}, method: {}, query: {} }); - var client = {}; - - return handler.getRefreshToken(request, client) - .then(function() { - model.getRefreshToken.callCount.should.equal(1); - model.getRefreshToken.firstCall.args.should.have.length(1); - model.getRefreshToken.firstCall.args[0].should.equal('bar'); - model.getRefreshToken.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); - - describe('revokeToken()', function() { - it('should call `model.revokeToken()`', function() { - var model = { - getRefreshToken: function() {}, - revokeToken: sinon.stub().returns({ accessToken: 'foo', client: {}, refreshTokenExpiresAt: new Date(new Date() / 2), user: {} }), - saveToken: function() {} - }; - var handler = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - var token = {}; - - return handler.revokeToken(token) - .then(function() { - model.revokeToken.callCount.should.equal(1); - model.revokeToken.firstCall.args.should.have.length(1); - model.revokeToken.firstCall.args[0].should.equal(token); - model.revokeToken.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - - it('should not call `model.revokeToken()`', function() { - var model = { - getRefreshToken: function() {}, - revokeToken: sinon.stub().returns({ accessToken: 'foo', client: {}, refreshTokenExpiresAt: new Date(new Date() / 2), user: {} }), - saveToken: function() {} - }; - var handler = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model, alwaysIssueNewRefreshToken: false }); - var token = {}; - - return handler.revokeToken(token) - .then(function() { - model.revokeToken.callCount.should.equal(0); - }) - .catch(should.fail); - }); - - it('should not call `model.revokeToken()`', function() { - var model = { - getRefreshToken: function() {}, - revokeToken: sinon.stub().returns({ accessToken: 'foo', client: {}, refreshTokenExpiresAt: new Date(new Date() / 2), user: {} }), - saveToken: function() {} - }; - var handler = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model, alwaysIssueNewRefreshToken: true }); - var token = {}; - - return handler.revokeToken(token) - .then(function() { - model.revokeToken.callCount.should.equal(1); - model.revokeToken.firstCall.args.should.have.length(1); - model.revokeToken.firstCall.args[0].should.equal(token); - model.revokeToken.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); - - describe('saveToken()', function() { - it('should call `model.saveToken()`', function() { - var client = {}; - var user = {}; - var model = { - getRefreshToken: function() {}, - revokeToken: function() {}, - saveToken: sinon.stub().returns(true) - }; - var handler = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model }); - - sinon.stub(handler, 'generateAccessToken').returns('foo'); - sinon.stub(handler, 'generateRefreshToken').returns('bar'); - sinon.stub(handler, 'getAccessTokenExpiresAt').returns('biz'); - sinon.stub(handler, 'getRefreshTokenExpiresAt').returns('baz'); - - return handler.saveToken(user, client, 'foobar') - .then(function() { - model.saveToken.callCount.should.equal(1); - model.saveToken.firstCall.args.should.have.length(3); - model.saveToken.firstCall.args[0].should.eql({ accessToken: 'foo', accessTokenExpiresAt: 'biz', refreshToken: 'bar', refreshTokenExpiresAt: 'baz', scope: 'foobar' }); - model.saveToken.firstCall.args[1].should.equal(client); - model.saveToken.firstCall.args[2].should.equal(user); - model.saveToken.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - - it('should call `model.saveToken()` without refresh token', function() { - var client = {}; - var user = {}; - var model = { - getRefreshToken: function() {}, - revokeToken: function() {}, - saveToken: sinon.stub().returns(true) - }; - var handler = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model, alwaysIssueNewRefreshToken: false }); - - sinon.stub(handler, 'generateAccessToken').returns('foo'); - sinon.stub(handler, 'generateRefreshToken').returns('bar'); - sinon.stub(handler, 'getAccessTokenExpiresAt').returns('biz'); - sinon.stub(handler, 'getRefreshTokenExpiresAt').returns('baz'); - - return handler.saveToken(user, client, 'foobar') - .then(function() { - model.saveToken.callCount.should.equal(1); - model.saveToken.firstCall.args.should.have.length(3); - model.saveToken.firstCall.args[0].should.eql({ accessToken: 'foo', accessTokenExpiresAt: 'biz', scope: 'foobar' }); - model.saveToken.firstCall.args[1].should.equal(client); - model.saveToken.firstCall.args[2].should.equal(user); - model.saveToken.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - - it('should call `model.saveToken()` with refresh token', function() { - var client = {}; - var user = {}; - var model = { - getRefreshToken: function() {}, - revokeToken: function() {}, - saveToken: sinon.stub().returns(true) - }; - var handler = new RefreshTokenGrantType({ accessTokenLifetime: 120, model: model, alwaysIssueNewRefreshToken: true}); - - sinon.stub(handler, 'generateAccessToken').returns('foo'); - sinon.stub(handler, 'generateRefreshToken').returns('bar'); - sinon.stub(handler, 'getAccessTokenExpiresAt').returns('biz'); - sinon.stub(handler, 'getRefreshTokenExpiresAt').returns('baz'); - - return handler.saveToken(user, client, 'foobar') - .then(function() { - model.saveToken.callCount.should.equal(1); - model.saveToken.firstCall.args.should.have.length(3); - model.saveToken.firstCall.args[0].should.eql({ accessToken: 'foo', accessTokenExpiresAt: 'biz', refreshToken: 'bar', refreshTokenExpiresAt: 'baz', scope: 'foobar' }); - model.saveToken.firstCall.args[1].should.equal(client); - model.saveToken.firstCall.args[2].should.equal(user); - model.saveToken.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); -}); diff --git a/test/unit/handlers/authenticate-handler.spec.ts b/test/unit/handlers/authenticate-handler.spec.ts new file mode 100755 index 000000000..442ef0f3c --- /dev/null +++ b/test/unit/handlers/authenticate-handler.spec.ts @@ -0,0 +1,166 @@ +import * as should from 'should'; +import * as sinon from 'sinon'; +import { ServerError } from '../../../lib/errors'; +import { AuthenticateHandler } from '../../../lib/handlers'; +import { Request } from '../../../lib/request'; + +/** + * Test `AuthenticateHandler`. + */ + +describe('AuthenticateHandler', () => { + describe('getTokenFromRequest()', () => { + describe('with bearer token in the request authorization header', () => { + it('should call `getTokenFromRequestHeader()`', () => { + const handler: any = new AuthenticateHandler({ + model: { getAccessToken() {} }, + }); + const request = new Request({ + body: {}, + headers: { Authorization: 'Bearer foo' }, + method: 'ANY', + query: {}, + }); + + sinon.stub(handler, 'getTokenFromRequestHeader'); + + handler.getTokenFromRequest(request); + + handler.getTokenFromRequestHeader.callCount.should.equal(1); + handler.getTokenFromRequestHeader.firstCall.args[0].should.equal( + request, + ); + handler.getTokenFromRequestHeader.restore(); + }); + }); + + describe('with bearer token in the request query', () => { + it('should call `getTokenFromRequestQuery()`', () => { + const handler: any = new AuthenticateHandler({ + model: { getAccessToken() {} }, + }); + const request = new Request({ + body: {}, + headers: {}, + method: 'ANY', + query: { access_token: 'foo' }, + }); + + sinon.stub(handler, 'getTokenFromRequestQuery'); + + handler.getTokenFromRequest(request); + + handler.getTokenFromRequestQuery.callCount.should.equal(1); + handler.getTokenFromRequestQuery.firstCall.args[0].should.equal( + request, + ); + handler.getTokenFromRequestQuery.restore(); + }); + }); + + describe('with bearer token in the request body', () => { + it('should call `getTokenFromRequestBody()`', () => { + const handler: any = new AuthenticateHandler({ + model: { getAccessToken() {} }, + }); + const request = new Request({ + body: { access_token: 'foo' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + sinon.stub(handler, 'getTokenFromRequestBody'); + + handler.getTokenFromRequest(request); + + handler.getTokenFromRequestBody.callCount.should.equal(1); + handler.getTokenFromRequestBody.firstCall.args[0].should.equal(request); + handler.getTokenFromRequestBody.restore(); + }); + }); + }); + + describe('getAccessToken()', () => { + it('should call `model.getAccessToken()`', () => { + const model = { + getAccessToken: sinon.stub().returns({ user: {} }), + }; + const handler = new AuthenticateHandler({ model }); + + return handler + .getAccessToken('foo') + .then(() => { + model.getAccessToken.callCount.should.equal(1); + model.getAccessToken.firstCall.args.should.have.length(1); + model.getAccessToken.firstCall.args[0].should.equal('foo'); + model.getAccessToken.firstCall.thisValue.should.equal(model); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + + describe('validateAccessToken()', () => { + it('should fail if token has no valid `accessTokenExpiresAt` date', () => { + const model = { + getAccessToken() {}, + }; + const handler = new AuthenticateHandler({ model }); + + let failed = false; + try { + handler.validateAccessToken({ + user: {}, + } as any); + } catch (err) { + err.should.be.an.instanceOf(ServerError); + failed = true; + } + failed.should.equal(true); + }); + + it('should succeed if token has valid `accessTokenExpiresAt` date', () => { + const model = { + getAccessToken() {}, + }; + const handler = new AuthenticateHandler({ model }); + try { + handler.validateAccessToken({ + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + } as any); + } catch (err) { + should.fail('should.fail', ''); + } + }); + }); + + describe('verifyScope()', () => { + it('should call `model.getAccessToken()` if scope is defined', () => { + const model = { + getAccessToken() {}, + verifyScope: sinon.stub().returns(true), + }; + const handler = new AuthenticateHandler({ + addAcceptedScopesHeader: true, + addAuthorizedScopesHeader: true, + model, + scope: 'bar', + }); + + return handler + .verifyScope('foo' as any) + .then(() => { + model.verifyScope.callCount.should.equal(1); + model.verifyScope.firstCall.args.should.have.length(2); + model.verifyScope.firstCall.args[0].should.equal('foo', 'bar'); + model.verifyScope.firstCall.thisValue.should.equal(model); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); +}); diff --git a/test/unit/handlers/authenticate-handler_test.js b/test/unit/handlers/authenticate-handler_test.js deleted file mode 100644 index 2adac7884..000000000 --- a/test/unit/handlers/authenticate-handler_test.js +++ /dev/null @@ -1,153 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var AuthenticateHandler = require('../../../lib/handlers/authenticate-handler'); -var Request = require('../../../lib/request'); -var sinon = require('sinon'); -var should = require('should'); -var ServerError = require('../../../lib/errors/server-error'); - -/** - * Test `AuthenticateHandler`. - */ - -describe('AuthenticateHandler', function() { - describe('getTokenFromRequest()', function() { - describe('with bearer token in the request authorization header', function() { - it('should call `getTokenFromRequestHeader()`', function() { - var handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); - var request = new Request({ - body: {}, - headers: { 'Authorization': 'Bearer foo' }, - method: {}, - query: {} - }); - - sinon.stub(handler, 'getTokenFromRequestHeader'); - - handler.getTokenFromRequest(request); - - handler.getTokenFromRequestHeader.callCount.should.equal(1); - handler.getTokenFromRequestHeader.firstCall.args[0].should.equal(request); - handler.getTokenFromRequestHeader.restore(); - }); - }); - - describe('with bearer token in the request query', function() { - it('should call `getTokenFromRequestQuery()`', function() { - var handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); - var request = new Request({ - body: {}, - headers: {}, - method: {}, - query: { access_token: 'foo' } - }); - - sinon.stub(handler, 'getTokenFromRequestQuery'); - - handler.getTokenFromRequest(request); - - handler.getTokenFromRequestQuery.callCount.should.equal(1); - handler.getTokenFromRequestQuery.firstCall.args[0].should.equal(request); - handler.getTokenFromRequestQuery.restore(); - }); - }); - - describe('with bearer token in the request body', function() { - it('should call `getTokenFromRequestBody()`', function() { - var handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); - var request = new Request({ - body: { access_token: 'foo' }, - headers: {}, - method: {}, - query: {} - }); - - sinon.stub(handler, 'getTokenFromRequestBody'); - - handler.getTokenFromRequest(request); - - handler.getTokenFromRequestBody.callCount.should.equal(1); - handler.getTokenFromRequestBody.firstCall.args[0].should.equal(request); - handler.getTokenFromRequestBody.restore(); - }); - }); - }); - - describe('getAccessToken()', function() { - it('should call `model.getAccessToken()`', function() { - var model = { - getAccessToken: sinon.stub().returns({ user: {} }) - }; - var handler = new AuthenticateHandler({ model: model }); - - return handler.getAccessToken('foo') - .then(function() { - model.getAccessToken.callCount.should.equal(1); - model.getAccessToken.firstCall.args.should.have.length(1); - model.getAccessToken.firstCall.args[0].should.equal('foo'); - model.getAccessToken.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); - - describe('validateAccessToken()', function() { - it('should fail if token has no valid `accessTokenExpiresAt` date', function() { - var model = { - getAccessToken: function() {} - }; - var handler = new AuthenticateHandler({ model: model }); - - var failed = false; - try { - handler.validateAccessToken({ - user: {} - }); - } - catch (err) { - err.should.be.an.instanceOf(ServerError); - failed = true; - } - failed.should.equal(true); - }); - - it('should succeed if token has valid `accessTokenExpiresAt` date', function() { - var model = { - getAccessToken: function() {} - }; - var handler = new AuthenticateHandler({ model: model }); - try { - handler.validateAccessToken({ - user: {}, - accessTokenExpiresAt: new Date(new Date().getTime() + 10000) - }); - } - catch (err) { - should.fail(); - } - }); - }); - - describe('verifyScope()', function() { - it('should call `model.getAccessToken()` if scope is defined', function() { - var model = { - getAccessToken: function() {}, - verifyScope: sinon.stub().returns(true) - }; - var handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: true, model: model, scope: 'bar' }); - - return handler.verifyScope('foo') - .then(function() { - model.verifyScope.callCount.should.equal(1); - model.verifyScope.firstCall.args.should.have.length(2); - model.verifyScope.firstCall.args[0].should.equal('foo', 'bar'); - model.verifyScope.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); -}); diff --git a/test/unit/handlers/authorize-handler.spec.ts b/test/unit/handlers/authorize-handler.spec.ts new file mode 100755 index 000000000..04c010f08 --- /dev/null +++ b/test/unit/handlers/authorize-handler.spec.ts @@ -0,0 +1,140 @@ +import * as should from 'should'; +import * as sinon from 'sinon'; +import { AuthorizeHandler } from '../../../lib/handlers'; +import { Request } from '../../../lib/request'; +import { Response } from '../../../lib/response'; + +/** + * Test `AuthorizeHandler`. + */ + +describe('AuthorizeHandler', () => { + // describe('generateAuthorizationCode()', () => { + // it('should call `model.generateAuthorizationCode()`', async () => { + // const model = { + // generateAuthorizationCode: sinon.stub().returns({}), + // getAccessToken() {}, + // getClient() {}, + // saveAuthorizationCode() {}, + // }; + // const handler = new AuthorizeHandler({ + // authorizationCodeLifetime: 120, + // model, + // }); + // try { + // await handler.generateAuthorizationCode({}, {}, {}); + // model.generateAuthorizationCode.callCount.should.equal(1); + // model.generateAuthorizationCode.firstCall.thisValue.should.equal(model); + // } catch (error) { + // should.fail('should.fail', ''); + // } + // }); + // }); + + describe('getClient()', () => { + it('should call `model.getClient()`', async () => { + const model = { + getAccessToken() {}, + getClient: sinon.stub().returns( + Promise.resolve({ + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }), + ), + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: 'ANY', + query: {}, + }); + try { + await handler.getClient(request); + model.getClient.callCount.should.equal(1); + model.getClient.firstCall.args.should.have.length(1); + model.getClient.firstCall.args[0].should.equal(12345); + model.getClient.firstCall.thisValue.should.equal(model); + } catch (error) { + should.fail('should.fail', ''); + } + }); + }); + + describe('getUser()', () => { + it('should call `authenticateHandler.getUser()`', () => { + const authenticateHandler = { + handle: sinon.stub().returns(Promise.resolve({})), + }; + const model = { + getClient() {}, + saveAuthorizationCode() {}, + }; + const handler = new AuthorizeHandler({ + authenticateHandler, + authorizationCodeLifetime: 120, + model, + }); + const request = new Request({ + body: {}, + headers: {}, + method: 'ANY', + query: {}, + }); + const response = new Response(); + + return handler + .getUser(request, response) + .then(() => { + authenticateHandler.handle.callCount.should.equal(1); + authenticateHandler.handle.firstCall.args.should.have.length(2); + authenticateHandler.handle.firstCall.args[0].should.equal(request); + authenticateHandler.handle.firstCall.args[1].should.equal(response); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); + + // describe('saveAuthorizationCode()', () => { + // it('should call `model.saveAuthorizationCode()`', () => { + // const model = { + // getAccessToken() {}, + // getClient() {}, + // saveAuthorizationCode: sinon.stub().returns({}), + // }; + // const handler = new AuthorizeHandler({ + // authorizationCodeLifetime: 120, + // model, + // }); + // return handler + // .saveAuthorizationCode( + // 'foo', + // 'bar' as any, + // 'qux', + // 'biz' as any, + // 'baz', + // 'boz' as any, + // ) + // .then(() => { + // model.saveAuthorizationCode.callCount.should.equal(1); + // model.saveAuthorizationCode.firstCall.args.should.have.length(3); + // model.saveAuthorizationCode.firstCall.args[0].should.eql({ + // authorizationCode: 'foo', + // expiresAt: 'bar', + // redirectUri: 'baz', + // scope: 'qux', + // }); + // model.saveAuthorizationCode.firstCall.args[1].should.equal('biz'); + // model.saveAuthorizationCode.firstCall.args[2].should.equal('boz'); + // model.saveAuthorizationCode.firstCall.thisValue.should.equal(model); + // }) + // .catch(() => should.fail('should.fail', '')); + // }); + // }); +}); diff --git a/test/unit/handlers/authorize-handler_test.js b/test/unit/handlers/authorize-handler_test.js deleted file mode 100644 index fe9b6b1d7..000000000 --- a/test/unit/handlers/authorize-handler_test.js +++ /dev/null @@ -1,102 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var AuthorizeHandler = require('../../../lib/handlers/authorize-handler'); -var Request = require('../../../lib/request'); -var Response = require('../../../lib/response'); -var Promise = require('bluebird'); -var sinon = require('sinon'); -var should = require('should'); - -/** - * Test `AuthorizeHandler`. - */ - -describe('AuthorizeHandler', function() { - describe('generateAuthorizationCode()', function() { - it('should call `model.generateAuthorizationCode()`', function() { - var model = { - generateAuthorizationCode: sinon.stub().returns({}), - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - - return handler.generateAuthorizationCode() - .then(function() { - model.generateAuthorizationCode.callCount.should.equal(1); - model.generateAuthorizationCode.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); - - describe('getClient()', function() { - it('should call `model.getClient()`', function() { - var model = { - getAccessToken: function() {}, - getClient: sinon.stub().returns({ grants: ['authorization_code'], redirectUris: ['http://example.com/cb'] }), - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, method: {}, query: {} }); - - return handler.getClient(request) - .then(function() { - model.getClient.callCount.should.equal(1); - model.getClient.firstCall.args.should.have.length(2); - model.getClient.firstCall.args[0].should.equal(12345); - model.getClient.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); - - describe('getUser()', function() { - it('should call `authenticateHandler.getUser()`', function() { - var authenticateHandler = { handle: sinon.stub().returns(Promise.resolve({})) }; - var model = { - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authenticateHandler: authenticateHandler, authorizationCodeLifetime: 120, model: model }); - var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); - var response = new Response(); - - return handler.getUser(request, response) - .then(function() { - authenticateHandler.handle.callCount.should.equal(1); - authenticateHandler.handle.firstCall.args.should.have.length(2); - authenticateHandler.handle.firstCall.args[0].should.equal(request); - authenticateHandler.handle.firstCall.args[1].should.equal(response); - }) - .catch(should.fail); - }); - }); - - describe('saveAuthorizationCode()', function() { - it('should call `model.saveAuthorizationCode()`', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: sinon.stub().returns({}) - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - - return handler.saveAuthorizationCode('foo', 'bar', 'qux', 'biz', 'baz', 'boz') - .then(function() { - model.saveAuthorizationCode.callCount.should.equal(1); - model.saveAuthorizationCode.firstCall.args.should.have.length(3); - model.saveAuthorizationCode.firstCall.args[0].should.eql({ authorizationCode: 'foo', expiresAt: 'bar', redirectUri: 'baz', scope: 'qux' }); - model.saveAuthorizationCode.firstCall.args[1].should.equal('biz'); - model.saveAuthorizationCode.firstCall.args[2].should.equal('boz'); - model.saveAuthorizationCode.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); -}); diff --git a/test/unit/handlers/revoke-handlers.spec.ts b/test/unit/handlers/revoke-handlers.spec.ts new file mode 100644 index 000000000..70fc355ea --- /dev/null +++ b/test/unit/handlers/revoke-handlers.spec.ts @@ -0,0 +1,126 @@ +import * as should from 'should'; +import * as sinon from 'sinon'; +import { RevokeHandler } from '../../../lib/handlers'; +import { Request } from '../../../lib/request'; + +/** + * Test `RevokeHandler`. + */ + +describe('RevokeHandler', () => { + describe('handleRevokeToken()', () => { + it('should call `model.getAccessToken()` and `model.getRefreshToken()`', () => { + const model = { + getClient() {}, + revokeToken: sinon.stub().returns(true), + getRefreshToken: sinon.stub().returns({ + refreshToken: 'hash', + client: {}, + refreshTokenExpiresAt: new Date(Date.now() * 2), + user: {}, + }), + getAccessToken: sinon.stub().returns(false), + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { token: 'foo' }, + headers: {}, + method: 'ANY', + query: {}, + }); + const client = {}; + + return handler + .handleRevokeToken(request, client) + .then(() => { + model.getAccessToken.callCount.should.equal(1); + model.getAccessToken.firstCall.args[0].should.equal('foo'); + model.getRefreshToken.callCount.should.equal(1); + model.getRefreshToken.firstCall.args[0].should.equal('foo'); + }) + .catch(should.fail); + }); + }); + + describe('getClient()', () => { + it('should call `model.getClient()`', () => { + const model = { + getClient: sinon.stub().returns({ grants: ['password'] }), + revokeToken() {}, + getRefreshToken() {}, + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const request = new Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return handler + .getClient(request) + .then(() => { + model.getClient.callCount.should.equal(1); + model.getClient.firstCall.args.should.have.length(2); + model.getClient.firstCall.args[0].should.equal(12345); + model.getClient.firstCall.args[1].should.equal('secret'); + }) + .catch(should.fail); + }); + }); + + describe('getRefreshToken()', () => { + it('should call `model.getRefreshToken()`', () => { + const model = { + getClient() {}, + revokeToken() {}, + getAccessToken() {}, + getRefreshToken: sinon.stub().returns({ + refreshToken: 'hash', + client: {}, + refreshTokenExpiresAt: new Date(Date.now() * 2), + user: {}, + }), + }; + const handler: any = new RevokeHandler({ model }); + const token = 'hash'; + const client = {}; + + return handler + .getRefreshToken(token, client) + .then(() => { + model.getRefreshToken.callCount.should.equal(1); + model.getRefreshToken.firstCall.args.should.have.length(1); + model.getRefreshToken.firstCall.args[0].should.equal(token); + }) + .catch(should.fail); + }); + }); + + describe('revokeToken()', () => { + it('should call `model.revokeToken()`', () => { + const model = { + getClient() {}, + revokeToken: sinon.stub().returns(true), + getRefreshToken: sinon.stub().returns({ + refreshToken: 'hash', + client: {}, + refreshTokenExpiresAt: new Date(Date.now() * 2), + user: {}, + }), + getAccessToken() {}, + }; + const handler: any = new RevokeHandler({ model }); + const token = 'hash'; + + return handler + .revokeToken(token) + .then(() => { + model.revokeToken.callCount.should.equal(1); + model.revokeToken.firstCall.args.should.have.length(1); + }) + .catch(should.fail); + }); + }); +}); diff --git a/test/unit/handlers/token-handler.spec.ts b/test/unit/handlers/token-handler.spec.ts new file mode 100755 index 000000000..53fcd36cf --- /dev/null +++ b/test/unit/handlers/token-handler.spec.ts @@ -0,0 +1,45 @@ +import * as should from 'should'; +import * as sinon from 'sinon'; +import { TokenHandler } from '../../../lib/handlers'; +import { Request } from '../../../lib/request'; + +/** + * Test `TokenHandler`. + */ + +describe('TokenHandler', () => { + describe('getClient()', () => { + it('should call `model.getClient()`', () => { + const model = { + getClient: sinon + .stub() + .returns(Promise.resolve({ grants: ['password'] })), + saveToken() {}, + }; + const handler = new TokenHandler({ + accessTokenLifetime: 120, + model, + refreshTokenLifetime: 120, + }); + const request = new Request({ + body: { client_id: 12345, client_secret: 'secret' }, + headers: {}, + method: 'ANY', + query: {}, + }); + + return handler + .getClient(request, {}) + .then(() => { + model.getClient.callCount.should.equal(1); + model.getClient.firstCall.args.should.have.length(2); + model.getClient.firstCall.args[0].should.equal(12345); + model.getClient.firstCall.args[1].should.equal('secret'); + model.getClient.firstCall.thisValue.should.equal(model); + }) + .catch(() => { + should.fail('should.fail', ''); + }); + }); + }); +}); diff --git a/test/unit/handlers/token-handler_test.js b/test/unit/handlers/token-handler_test.js deleted file mode 100644 index 2b37cd05a..000000000 --- a/test/unit/handlers/token-handler_test.js +++ /dev/null @@ -1,37 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var Request = require('../../../lib/request'); -var TokenHandler = require('../../../lib/handlers/token-handler'); -var sinon = require('sinon'); -var should = require('should'); - -/** - * Test `TokenHandler`. - */ - -describe('TokenHandler', function() { - describe('getClient()', function() { - it('should call `model.getClient()`', function() { - var model = { - getClient: sinon.stub().returns({ grants: ['password'] }), - saveToken: function() {} - }; - var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); - var request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, method: {}, query: {} }); - - return handler.getClient(request) - .then(function() { - model.getClient.callCount.should.equal(1); - model.getClient.firstCall.args.should.have.length(2); - model.getClient.firstCall.args[0].should.equal(12345); - model.getClient.firstCall.args[1].should.equal('secret'); - model.getClient.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); -}); diff --git a/test/unit/models/token-model.spec.ts b/test/unit/models/token-model.spec.ts new file mode 100755 index 000000000..b59f488c9 --- /dev/null +++ b/test/unit/models/token-model.spec.ts @@ -0,0 +1,25 @@ +import { TokenModel } from '../../../lib/models'; + +/** + * Test `Server`. + */ + +describe('Model', () => { + describe('constructor()', () => { + it('should calculate `accessTokenLifetime` if `accessTokenExpiresAt` is set', () => { + const atExpiresAt = new Date(); + atExpiresAt.setHours(new Date().getHours() + 1); + + const data = { + accessToken: 'foo', + client: 'bar', + user: 'tar', + accessTokenExpiresAt: atExpiresAt, + }; + + const model = new TokenModel(data); + model.accessTokenLifetime.should.be.Number(); + model.accessTokenLifetime.should.be.approximately(3600, 2); + }); + }); +}); diff --git a/test/unit/models/token-model_test.js b/test/unit/models/token-model_test.js deleted file mode 100644 index 3d899951c..000000000 --- a/test/unit/models/token-model_test.js +++ /dev/null @@ -1,25 +0,0 @@ -var TokenModel = require('../../../lib/models/token-model'); - -/** - * Test `Server`. - */ - -describe('Model', function() { - describe('constructor()', function() { - it('should calculate `accessTokenLifetime` if `accessTokenExpiresAt` is set', function() { - var atExpiresAt = new Date(); - atExpiresAt.setHours(new Date().getHours() + 1); - - var data = { - accessToken: 'foo', - client: 'bar', - user: 'tar', - accessTokenExpiresAt: atExpiresAt - }; - - var model = new TokenModel(data); - model.accessTokenLifetime.should.be.Number; - model.accessTokenLifetime.should.be.approximately(3600, 2); - }); - }); -}); diff --git a/test/unit/request.spec.ts b/test/unit/request.spec.ts new file mode 100755 index 000000000..16635b1c3 --- /dev/null +++ b/test/unit/request.spec.ts @@ -0,0 +1,170 @@ +import * as should from 'should'; +import { Request } from '../../lib/request'; + +/** + * Test `Request`. + */ + +function generateBaseRequest() { + return { + query: { + foo: 'bar', + } as any, + method: 'GET', + headers: { + bar: 'foo', + } as any, + body: { + foobar: 'barfoo', + } as any, + } as any; +} + +describe('Request', () => { + it('should instantiate with a basic request', () => { + const originalRequest = generateBaseRequest(); + + const request = new Request(originalRequest); + request.headers.should.eql(originalRequest.headers); + request.method.should.eql(originalRequest.method); + request.query.should.eql(originalRequest.query); + request.body.should.eql(originalRequest.body); + }); + + it('should allow a request to be passed without a body', () => { + const originalRequest = generateBaseRequest(); + delete originalRequest.body; + + const request = new Request(originalRequest); + request.headers.should.eql(originalRequest.headers); + request.method.should.eql(originalRequest.method); + request.query.should.eql(originalRequest.query); + request.body.should.eql({}); + }); + + it('should throw if headers are not passed to the constructor', () => { + const originalRequest = generateBaseRequest(); + delete originalRequest.headers; + + (() => { + new Request(originalRequest); + }).should.throw('Missing parameter: `headers`'); + }); + + it('should throw if query string isnt passed to the constructor', () => { + const originalRequest = generateBaseRequest(); + delete originalRequest.query; + + (() => { + new Request(originalRequest); + }).should.throw('Missing parameter: `query`'); + }); + + it('should throw if method isnt passed to the constructor', () => { + const originalRequest = generateBaseRequest(); + delete originalRequest.method; + + (() => { + new Request(originalRequest); + }).should.throw('Missing parameter: `method`'); + }); + + it('should convert all header keys to lowercase', () => { + const originalRequest = generateBaseRequest(); + originalRequest.headers = { + Foo: 'bar', + BAR: 'foo', + } as any; + + const request = new Request(originalRequest); + request.headers.foo.should.eql('bar'); + request.headers.bar.should.eql('foo'); + should.not.exist(request.headers.Foo); + should.not.exist(request.headers.BAR); + }); + + it('should include additional properties passed in the request', () => { + const originalRequest = generateBaseRequest(); + originalRequest.custom = { + newFoo: 'newBar', + }; + + originalRequest.custom2 = { + newBar: 'newFoo', + }; + + const request: any = new Request(originalRequest); + request.headers.should.eql(originalRequest.headers); + request.method.should.eql(originalRequest.method); + request.query.should.eql(originalRequest.query); + request.body.should.eql(originalRequest.body); + request.custom.should.eql(originalRequest.custom); + request.custom2.should.eql(originalRequest.custom2); + }); + + it('should include additional properties passed in the request', () => { + const originalRequest = generateBaseRequest(); + originalRequest.custom = { + newFoo: 'newBar', + }; + + originalRequest.custom2 = { + newBar: 'newFoo', + }; + + const request: any = new Request(originalRequest); + request.headers.should.eql(originalRequest.headers); + request.method.should.eql(originalRequest.method); + request.query.should.eql(originalRequest.query); + request.body.should.eql(originalRequest.body); + request.custom.should.eql(originalRequest.custom); + request.custom2.should.eql(originalRequest.custom2); + }); + + it('should allow getting of headers using `request.get`', () => { + const originalRequest = generateBaseRequest(); + + const request = new Request(originalRequest); + request.get('bar').should.eql(originalRequest.headers.bar); + }); + + it('should allow getting of headers using `request.get`', () => { + const originalRequest = generateBaseRequest(); + + const request = new Request(originalRequest); + request.get('bar').should.eql(originalRequest.headers.bar); + }); + + it('should allow getting of headers using `request.get`', () => { + const originalRequest = generateBaseRequest(); + + const request = new Request(originalRequest); + request.get('bar').should.eql(originalRequest.headers.bar); + }); + + it('should validate the content-type', () => { + const originalRequest = generateBaseRequest(); + originalRequest.headers['content-type'] = + 'application/x-www-form-urlencoded'; + originalRequest.headers['content-length'] = JSON.stringify( + originalRequest.body, + ).length; + + const request = new Request(originalRequest); + request + .is('application/x-www-form-urlencoded') + .should.eql('application/x-www-form-urlencoded'); + }); + + it('should return false if the content-type is invalid', () => { + const originalRequest = generateBaseRequest(); + originalRequest.headers['content-type'] = + 'application/x-www-form-urlencoded'; + originalRequest.headers['content-length'] = JSON.stringify( + originalRequest.body, + ).length; + + const request = new Request(originalRequest); + request.is('application/json').should.be.false(); + }); +}); diff --git a/test/unit/request_test.js b/test/unit/request_test.js deleted file mode 100644 index 458cb8f9e..000000000 --- a/test/unit/request_test.js +++ /dev/null @@ -1,168 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var Request = require('../../lib/request'); -var should = require('should'); - -/** - * Test `Request`. - */ - -function generateBaseRequest() { - return { - query: { - foo: 'bar' - }, - method: 'GET', - headers: { - bar: 'foo' - }, - body: { - foobar: 'barfoo' - } - }; -} - -describe('Request', function() { - it('should instantiate with a basic request', function() { - var originalRequest = generateBaseRequest(); - - var request = new Request(originalRequest); - request.headers.should.eql(originalRequest.headers); - request.method.should.eql(originalRequest.method); - request.query.should.eql(originalRequest.query); - request.body.should.eql(originalRequest.body); - }); - - it('should allow a request to be passed without a body', function() { - var originalRequest = generateBaseRequest(); - delete originalRequest.body; - - var request = new Request(originalRequest); - request.headers.should.eql(originalRequest.headers); - request.method.should.eql(originalRequest.method); - request.query.should.eql(originalRequest.query); - request.body.should.eql({}); - }); - - it('should throw if headers are not passed to the constructor', function() { - var originalRequest = generateBaseRequest(); - delete originalRequest.headers; - - (function() { - new Request(originalRequest); - }).should.throw('Missing parameter: `headers`'); - }); - - it('should throw if query string isn\'t passed to the constructor', function() { - var originalRequest = generateBaseRequest(); - delete originalRequest.query; - - (function() { - new Request(originalRequest); - }).should.throw('Missing parameter: `query`'); - }); - - it('should throw if method isn\'t passed to the constructor', function() { - var originalRequest = generateBaseRequest(); - delete originalRequest.method; - - (function() { - new Request(originalRequest); - }).should.throw('Missing parameter: `method`'); - }); - - it('should convert all header keys to lowercase', function() { - var originalRequest = generateBaseRequest(); - originalRequest.headers = { - Foo: 'bar', - BAR: 'foo' - }; - - var request = new Request(originalRequest); - request.headers.foo.should.eql('bar'); - request.headers.bar.should.eql('foo'); - should.not.exist(request.headers.Foo); - should.not.exist(request.headers.BAR); - }); - - it('should include additional properties passed in the request', function() { - var originalRequest = generateBaseRequest(); - originalRequest.custom = { - newFoo: 'newBar' - }; - - originalRequest.custom2 = { - newBar: 'newFoo' - }; - - var request = new Request(originalRequest); - request.headers.should.eql(originalRequest.headers); - request.method.should.eql(originalRequest.method); - request.query.should.eql(originalRequest.query); - request.body.should.eql(originalRequest.body); - request.custom.should.eql(originalRequest.custom); - request.custom2.should.eql(originalRequest.custom2); - }); - - it('should include additional properties passed in the request', function() { - var originalRequest = generateBaseRequest(); - originalRequest.custom = { - newFoo: 'newBar' - }; - - originalRequest.custom2 = { - newBar: 'newFoo' - }; - - var request = new Request(originalRequest); - request.headers.should.eql(originalRequest.headers); - request.method.should.eql(originalRequest.method); - request.query.should.eql(originalRequest.query); - request.body.should.eql(originalRequest.body); - request.custom.should.eql(originalRequest.custom); - request.custom2.should.eql(originalRequest.custom2); - }); - - it('should allow getting of headers using `request.get`', function() { - var originalRequest = generateBaseRequest(); - - var request = new Request(originalRequest); - request.get('bar').should.eql(originalRequest.headers.bar); - }); - - it('should allow getting of headers using `request.get`', function() { - var originalRequest = generateBaseRequest(); - - var request = new Request(originalRequest); - request.get('bar').should.eql(originalRequest.headers.bar); - }); - - it('should allow getting of headers using `request.get`', function() { - var originalRequest = generateBaseRequest(); - - var request = new Request(originalRequest); - request.get('bar').should.eql(originalRequest.headers.bar); - }); - - it('should validate the content-type', function() { - var originalRequest = generateBaseRequest(); - originalRequest.headers['content-type'] = 'application/x-www-form-urlencoded'; - originalRequest.headers['content-length'] = JSON.stringify(originalRequest.body).length; - - var request = new Request(originalRequest); - request.is('application/x-www-form-urlencoded').should.eql('application/x-www-form-urlencoded'); - }); - - it('should return false if the content-type is invalid', function() { - var originalRequest = generateBaseRequest(); - originalRequest.headers['content-type'] = 'application/x-www-form-urlencoded'; - originalRequest.headers['content-length'] = JSON.stringify(originalRequest.body).length; - - var request = new Request(originalRequest); - request.is('application/json').should.eql(false); - }); -}); diff --git a/test/unit/response.spec.ts b/test/unit/response.spec.ts new file mode 100755 index 000000000..2a2c3f9a9 --- /dev/null +++ b/test/unit/response.spec.ts @@ -0,0 +1,114 @@ +import * as should from 'should'; +import { Response } from '../../lib/response'; + +/** + * Test `Request`. + */ + +const generateBaseResponse = () => { + return { + headers: { + bar: 'foo', + } as any, + body: { + foobar: 'barfoo', + } as any, + } as any; +}; + +describe('Response', () => { + it('should instantiate with a basic request', () => { + const originalResponse = generateBaseResponse(); + + const response = new Response(originalResponse); + response.headers.should.eql(originalResponse.headers); + response.body.should.eql(originalResponse.body); + response.status.should.eql(200); + }); + + it('should allow a response to be passed without a body', () => { + const originalResponse = generateBaseResponse(); + delete originalResponse.body; + + const response = new Response(originalResponse); + response.headers.should.eql(originalResponse.headers); + response.body.should.eql({}); + response.status.should.eql(200); + }); + + it('should allow a response to be passed without headers', () => { + const originalResponse = generateBaseResponse(); + delete originalResponse.headers; + + const response = new Response(originalResponse); + response.headers.should.eql({}); + response.body.should.eql(originalResponse.body); + response.status.should.eql(200); + }); + + it('should convert all header keys to lowercase', () => { + const originalResponse = generateBaseResponse(); + originalResponse.headers = { + Foo: 'bar', + BAR: 'foo', + }; + + const response: any = new Response(originalResponse); + response.headers.foo.should.eql('bar'); + response.headers.bar.should.eql('foo'); + should.not.exist(response.headers.Foo); + should.not.exist(response.headers.BAR); + }); + + it('should include additional properties passed in the response', () => { + const originalResponse = generateBaseResponse(); + originalResponse.custom = { + newFoo: 'newBar', + }; + + originalResponse.custom2 = { + newBar: 'newFoo', + }; + + const response: any = new Response(originalResponse); + response.headers.should.eql(originalResponse.headers); + response.body.should.eql(originalResponse.body); + response.custom.should.eql(originalResponse.custom); + response.custom2.should.eql(originalResponse.custom2); + }); + + it('should allow getting of headers using `response.get`', () => { + const originalResponse = generateBaseResponse(); + + const response = new Response(originalResponse); + response.get('bar').should.eql(originalResponse.headers.bar); + }); + + it('should allow getting of headers using `response.get`', () => { + const originalResponse = generateBaseResponse(); + + const response = new Response(originalResponse); + response.get('bar').should.eql(originalResponse.headers.bar); + }); + + it('should allow setting of headers using `response.set`', () => { + const originalResponse = generateBaseResponse(); + + const response: any = new Response(originalResponse); + response.headers.should.eql(originalResponse.headers); + response.set('new_header', 'new_value'); + response.headers.bar.should.eql('foo'); + response.headers.new_header.should.eql('new_value'); + }); + + it('should process redirect', () => { + const originalResponse = generateBaseResponse(); + + const response: any = new Response(originalResponse); + response.headers.should.eql(originalResponse.headers); + response.status.should.eql(200); + response.redirect('http://foo.bar'); + response.headers.location.should.eql('http://foo.bar'); + response.status.should.eql(302); + }); +}); diff --git a/test/unit/response_test.js b/test/unit/response_test.js deleted file mode 100644 index c435e32f7..000000000 --- a/test/unit/response_test.js +++ /dev/null @@ -1,120 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var Response = require('../../lib/response'); -var should = require('should'); - -/** - * Test `Request`. - */ - -function generateBaseResponse() { - return { - headers: { - bar: 'foo' - }, - body: { - foobar: 'barfoo' - } - }; -} - -describe('Request', function() { - it('should instantiate with a basic request', function() { - var originalResponse = generateBaseResponse(); - - var response = new Response(originalResponse); - response.headers.should.eql(originalResponse.headers); - response.body.should.eql(originalResponse.body); - response.status.should.eql(200); - }); - - it('should allow a response to be passed without a body', function() { - var originalResponse = generateBaseResponse(); - delete originalResponse.body; - - var response = new Response(originalResponse); - response.headers.should.eql(originalResponse.headers); - response.body.should.eql({}); - response.status.should.eql(200); - }); - - it('should allow a response to be passed without headers', function() { - var originalResponse = generateBaseResponse(); - delete originalResponse.headers; - - var response = new Response(originalResponse); - response.headers.should.eql({}); - response.body.should.eql(originalResponse.body); - response.status.should.eql(200); - }); - - it('should convert all header keys to lowercase', function() { - var originalResponse = generateBaseResponse(); - originalResponse.headers = { - Foo: 'bar', - BAR: 'foo' - }; - - var response = new Response(originalResponse); - response.headers.foo.should.eql('bar'); - response.headers.bar.should.eql('foo'); - should.not.exist(response.headers.Foo); - should.not.exist(response.headers.BAR); - }); - - it('should include additional properties passed in the response', function() { - var originalResponse = generateBaseResponse(); - originalResponse.custom = { - newFoo: 'newBar' - }; - - originalResponse.custom2 = { - newBar: 'newFoo' - }; - - var response = new Response(originalResponse); - response.headers.should.eql(originalResponse.headers); - response.body.should.eql(originalResponse.body); - response.custom.should.eql(originalResponse.custom); - response.custom2.should.eql(originalResponse.custom2); - }); - - it('should allow getting of headers using `response.get`', function() { - var originalResponse = generateBaseResponse(); - - var response = new Response(originalResponse); - response.get('bar').should.eql(originalResponse.headers.bar); - }); - - it('should allow getting of headers using `response.get`', function() { - var originalResponse = generateBaseResponse(); - - var response = new Response(originalResponse); - response.get('bar').should.eql(originalResponse.headers.bar); - }); - - it('should allow setting of headers using `response.set`', function() { - var originalResponse = generateBaseResponse(); - - var response = new Response(originalResponse); - response.headers.should.eql(originalResponse.headers); - response.set('newheader', 'newvalue'); - response.headers.bar.should.eql('foo'); - response.headers.newheader.should.eql('newvalue'); - }); - - it('should process redirect', function() { - var originalResponse = generateBaseResponse(); - - var response = new Response(originalResponse); - response.headers.should.eql(originalResponse.headers); - response.status.should.eql(200); - response.redirect('http://foo.bar'); - response.headers.location.should.eql('http://foo.bar'); - response.status.should.eql(302); - }); -}); diff --git a/test/unit/server.spec.ts b/test/unit/server.spec.ts new file mode 100755 index 000000000..2ce3b4f7d --- /dev/null +++ b/test/unit/server.spec.ts @@ -0,0 +1,91 @@ +import * as sinon from 'sinon'; +import { + AuthenticateHandler, + AuthorizeHandler, + TokenHandler, +} from '../../lib/handlers'; +import { OAuth2Server as Server } from '../../lib/server'; + +const Authenticate: any = AuthenticateHandler; +const Authorize: any = AuthorizeHandler; +const Token: any = TokenHandler; +/** + * Test `Server`. + */ + +describe('Server', () => { + describe('authenticate()', () => { + it('should call `handle`', async () => { + const model = { + getAccessToken() {}, + }; + const server = new Server({ model }); + + sinon.stub(Authenticate.prototype, 'handle').returns(Promise.resolve()); + + await server.authenticate('foo' as any); + + Authenticate.prototype.handle.callCount.should.equal(1); + Authenticate.prototype.handle.firstCall.args[0].should.equal('foo'); + Authenticate.prototype.handle.restore(); + }); + + it('should map string passed as `options` to `options.scope`', async () => { + const model = { + getAccessToken() {}, + verifyScope() {}, + }; + const server = new Server({ model }); + + sinon.stub(Authenticate.prototype, 'handle').returns(Promise.resolve()); + + await server.authenticate('foo' as any, 'bar' as any, 'test'); + + Authenticate.prototype.handle.callCount.should.equal(1); + Authenticate.prototype.handle.firstCall.args[0].should.equal('foo'); + Authenticate.prototype.handle.firstCall.args[1].should.equal('bar'); + Authenticate.prototype.handle.firstCall.thisValue.should.have.property( + 'scope', + 'test', + ); + Authenticate.prototype.handle.restore(); + }); + }); + + describe('authorize()', () => { + it('should call `handle`', async () => { + const model = { + getAccessToken() {}, + getClient() {}, + saveAuthorizationCode() {}, + }; + const server = new Server({ model }); + + sinon.stub(Authorize.prototype, 'handle').returns(Promise.resolve()); + + await server.authorize('foo' as any, 'bar' as any); + + Authorize.prototype.handle.callCount.should.equal(1); + Authorize.prototype.handle.firstCall.args[0].should.equal('foo'); + Authorize.prototype.handle.restore(); + }); + }); + + describe('token()', () => { + it('should call `handle`', async () => { + const model = { + getClient() {}, + saveToken() {}, + }; + const server = new Server({ model }); + + sinon.stub(Token.prototype, 'handle').returns(Promise.resolve()); + + await server.token('foo' as any, 'bar' as any); + + Token.prototype.handle.callCount.should.equal(1); + Token.prototype.handle.firstCall.args[0].should.equal('foo'); + Token.prototype.handle.restore(); + }); + }); +}); diff --git a/test/unit/server_test.js b/test/unit/server_test.js deleted file mode 100644 index e7c343f0c..000000000 --- a/test/unit/server_test.js +++ /dev/null @@ -1,90 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var AuthenticateHandler = require('../../lib/handlers/authenticate-handler'); -var AuthorizeHandler = require('../../lib/handlers/authorize-handler'); -var Promise = require('bluebird'); -var Server = require('../../lib/server'); -var TokenHandler = require('../../lib/handlers/token-handler'); -var sinon = require('sinon'); - -/** - * Test `Server`. - */ - -describe('Server', function() { - describe('authenticate()', function() { - it('should call `handle`', function() { - var model = { - getAccessToken: function() {} - }; - var server = new Server({ model: model }); - - sinon.stub(AuthenticateHandler.prototype, 'handle').returns(Promise.resolve()); - - server.authenticate('foo'); - - AuthenticateHandler.prototype.handle.callCount.should.equal(1); - AuthenticateHandler.prototype.handle.firstCall.args[0].should.equal('foo'); - AuthenticateHandler.prototype.handle.restore(); - }); - - it('should map string passed as `options` to `options.scope`', function() { - var model = { - getAccessToken: function() {}, - verifyScope: function() {} - }; - var server = new Server({ model: model }); - - sinon.stub(AuthenticateHandler.prototype, 'handle').returns(Promise.resolve()); - - server.authenticate('foo', 'bar', 'test'); - - AuthenticateHandler.prototype.handle.callCount.should.equal(1); - AuthenticateHandler.prototype.handle.firstCall.args[0].should.equal('foo'); - AuthenticateHandler.prototype.handle.firstCall.args[1].should.equal('bar'); - AuthenticateHandler.prototype.handle.firstCall.thisValue.should.have.property('scope', 'test'); - AuthenticateHandler.prototype.handle.restore(); - }); - }); - - describe('authorize()', function() { - it('should call `handle`', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var server = new Server({ model: model }); - - sinon.stub(AuthorizeHandler.prototype, 'handle').returns(Promise.resolve()); - - server.authorize('foo', 'bar'); - - AuthorizeHandler.prototype.handle.callCount.should.equal(1); - AuthorizeHandler.prototype.handle.firstCall.args[0].should.equal('foo'); - AuthorizeHandler.prototype.handle.restore(); - }); - }); - - describe('token()', function() { - it('should call `handle`', function() { - var model = { - getClient: function() {}, - saveToken: function() {} - }; - var server = new Server({ model: model }); - - sinon.stub(TokenHandler.prototype, 'handle').returns(Promise.resolve()); - - server.token('foo', 'bar'); - - TokenHandler.prototype.handle.callCount.should.equal(1); - TokenHandler.prototype.handle.firstCall.args[0].should.equal('foo'); - TokenHandler.prototype.handle.restore(); - }); - }); -}); diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100755 index 000000000..56ee33060 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "sourceMap": true + }, + "exclude": ["node_modules", "test", "**/*spec.ts"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100755 index 000000000..b42af265a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "removeComments": true, + "target": "es2017", + "sourceMap": true, + "importHelpers": true, + "outDir": "./dist", + "baseUrl": ".", + "typeRoots": ["node_modules/@types"], + "downlevelIteration": false, + "emitDecoratorMetadata": false, + "experimentalDecorators": false, + "moduleResolution": "node", + "lib": ["es2018"] + }, + "exclude": ["node_modules", "./dist"] +} diff --git a/tslint.json b/tslint.json new file mode 100755 index 000000000..db341dfc4 --- /dev/null +++ b/tslint.json @@ -0,0 +1,28 @@ +{ + "extends": ["tslint:recommended", "tslint:all", "tslint:latest"], + "jsRules": { + "no-unused-expression": true + }, + "rules": { + "arrow-parens": false, + "completed-docs": false, + "file-name-casing": [true, "kebab-case"], + "no-object-literal-type-assertion": [false], + "interface-name": [false], + "max-line-length": [true, 120], + "member-access": [false], + "no-boolean-literal-compare": [false], + "member-ordering": [false], + "no-default-export": [false], + "prefer-function-over-method": [false], + "no-magic-numbers": [false], + "newline-per-chained-call": false, + "no-unsafe-any": false, + "object-literal-sort-keys": false, + "ordered-imports": [false], + "quotemark": [true, "single"], + "semicolon": [true, "always", "ignore-bound-class-methods"], + "strict-boolean-expressions": [false] + }, + "rulesDirectory": [] +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 000000000..7584c9dc3 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,2323 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" + integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== + dependencies: + "@babel/highlight" "^7.10.4" + +"@babel/helper-validator-identifier@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2" + integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw== + +"@babel/highlight@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143" + integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA== + dependencies: + "@babel/helper-validator-identifier" "^7.10.4" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@nodelib/fs.scandir@2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" + integrity sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw== + dependencies: + "@nodelib/fs.stat" "2.0.3" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.3", "@nodelib/fs.stat@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3" + integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976" + integrity sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ== + dependencies: + "@nodelib/fs.scandir" "2.1.3" + fastq "^1.6.0" + +"@sinonjs/commons@^1", "@sinonjs/commons@^1.3.0", "@sinonjs/commons@^1.4.0", "@sinonjs/commons@^1.7.0": + version "1.8.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.1.tgz#e7df00f98a203324f6dc7cc606cad9d4a8ab2217" + integrity sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw== + dependencies: + type-detect "4.0.8" + +"@sinonjs/formatio@^3.2.1": + version "3.2.2" + resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-3.2.2.tgz#771c60dfa75ea7f2d68e3b94c7e888a78781372c" + integrity sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ== + dependencies: + "@sinonjs/commons" "^1" + "@sinonjs/samsam" "^3.1.0" + +"@sinonjs/samsam@^3.1.0", "@sinonjs/samsam@^3.3.3": + version "3.3.3" + resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-3.3.3.tgz#46682efd9967b259b81136b9f120fd54585feb4a" + integrity sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ== + dependencies: + "@sinonjs/commons" "^1.3.0" + array-from "^2.1.1" + lodash "^4.17.15" + +"@sinonjs/text-encoding@^0.7.1": + version "0.7.1" + resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" + integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ== + +"@types/basic-auth@^1.1.2": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@types/basic-auth/-/basic-auth-1.1.3.tgz#a787ede8310804174fbbf3d6c623ab1ccedb02cd" + integrity sha512-W3rv6J0IGlxqgE2eQ2pTb0gBjaGtejQpJ6uaCjz3UQ65+TFTPC5/lAE+POfx1YLdjtxvejJzsIAfd3MxWiVmfg== + dependencies: + "@types/node" "*" + +"@types/color-name@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" + integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== + +"@types/json-schema@^7.0.3": + version "7.0.6" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" + integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw== + +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= + +"@types/mocha@^5.2.7": + version "5.2.7" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.7.tgz#315d570ccb56c53452ff8638738df60726d5b6ea" + integrity sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ== + +"@types/node@*": + version "14.11.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.11.2.tgz#2de1ed6670439387da1c9f549a2ade2b0a799256" + integrity sha512-jiE3QIxJ8JLNcb1Ps6rDbysDhN4xa8DJJvuC9prr6w+1tIh+QAbYyNF3tyiZNLDBIuBCf4KEcV2UvQm/V60xfA== + +"@types/node@^11.15.3": + version "11.15.27" + resolved "https://registry.yarnpkg.com/@types/node/-/node-11.15.27.tgz#2473edcb8a9a51e25332dc24692cbce19dc89dc2" + integrity sha512-LbLwyGC/ukDV0EbHFP1OCfs2V5h3vUS8ZXJJjS2L5YYg8rNkJe6Tl/yv+L+g94sbHllyXUCfUCn5+sZLBegvyw== + +"@types/sinon@^7.5.1": + version "7.5.2" + resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-7.5.2.tgz#5e2f1d120f07b9cda07e5dedd4f3bf8888fccdb9" + integrity sha512-T+m89VdXj/eidZyejvmoP9jivXgBDdkOSBVQjU9kF349NEx10QdPNGxHeZUaj1IlJ32/ewdyXJjnJxyxJroYwg== + +"@types/statuses@^1.5.0": + version "1.5.1" + resolved "https://registry.yarnpkg.com/@types/statuses/-/statuses-1.5.1.tgz#3e69c0cb1e188ebbae2531694efae119854afe27" + integrity sha512-y9Dt8XBpGLcMDhxs+2/wNq858FL6JSIr4AF9zVwFWm+VzmMx+iGytrGrt/6GpdRfx8gkNpmjTVYlDg0dbk4xGg== + +"@types/type-is@^1.6.3": + version "1.6.3" + resolved "https://registry.yarnpkg.com/@types/type-is/-/type-is-1.6.3.tgz#45285b3be846a4afc9d488910a8e4b7bc2e8a169" + integrity sha512-PNs5wHaNcBgCQG5nAeeZ7OvosrEsI9O4W2jAOO9BCCg4ux9ZZvH2+0iSCOIDBiKuQsiNS8CBlmfX9f5YBQ22cA== + dependencies: + "@types/node" "*" + +"@typescript-eslint/eslint-plugin@^4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.2.0.tgz#a3d5c11b377b7e18f3cd9c4e87d465fe9432669b" + integrity sha512-zBNRkzvLSwo6y5TG0DVcmshZIYBHKtmzD4N+LYnfTFpzc4bc79o8jNRSb728WV7A4Cegbs+MV5IRAj8BKBgOVQ== + dependencies: + "@typescript-eslint/experimental-utils" "4.2.0" + "@typescript-eslint/scope-manager" "4.2.0" + debug "^4.1.1" + functional-red-black-tree "^1.0.1" + regexpp "^3.0.0" + semver "^7.3.2" + tsutils "^3.17.1" + +"@typescript-eslint/experimental-utils@4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.2.0.tgz#3d0b5cd4aa61f5eb7aa1e873dea0db1410b062d2" + integrity sha512-5BBj6BjgHEndBaQQpUVzRIPERz03LBc0MCQkHwUaH044FJFL08SwWv/sQftk7gf0ShZ2xZysz0LTwCwNt4Xu3w== + dependencies: + "@types/json-schema" "^7.0.3" + "@typescript-eslint/scope-manager" "4.2.0" + "@typescript-eslint/types" "4.2.0" + "@typescript-eslint/typescript-estree" "4.2.0" + eslint-scope "^5.0.0" + eslint-utils "^2.0.0" + +"@typescript-eslint/parser@4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.2.0.tgz#1879ef400abd73d972e20f14c3522e5b343d1d1b" + integrity sha512-54jJ6MwkOtowpE48C0QJF9iTz2/NZxfKVJzv1ha5imigzHbNSLN9yvbxFFH1KdlRPQrlR8qxqyOvLHHxd397VA== + dependencies: + "@typescript-eslint/scope-manager" "4.2.0" + "@typescript-eslint/types" "4.2.0" + "@typescript-eslint/typescript-estree" "4.2.0" + debug "^4.1.1" + +"@typescript-eslint/scope-manager@4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.2.0.tgz#d10e6854a65e175b22a28265d372a97c8cce4bfc" + integrity sha512-Tb402cxxObSxWIVT+PnBp5ruT2V/36yj6gG4C9AjkgRlZpxrLAzWDk3neen6ToMBGeGdxtnfFLoJRUecGz9mYQ== + dependencies: + "@typescript-eslint/types" "4.2.0" + "@typescript-eslint/visitor-keys" "4.2.0" + +"@typescript-eslint/types@4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.2.0.tgz#6f6b094329e72040f173123832397c7c0b910fc8" + integrity sha512-xkv5nIsxfI/Di9eVwN+G9reWl7Me9R5jpzmZUch58uQ7g0/hHVuGUbbn4NcxcM5y/R4wuJIIEPKPDb5l4Fdmwg== + +"@typescript-eslint/typescript-estree@4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.2.0.tgz#9d746240991c305bf225ad5e96cbf57e7fea0551" + integrity sha512-iWDLCB7z4MGkLipduF6EOotdHNtgxuNKnYD54nMS/oitFnsk4S3S/TE/UYXQTra550lHtlv9eGmp+dvN9pUDtA== + dependencies: + "@typescript-eslint/types" "4.2.0" + "@typescript-eslint/visitor-keys" "4.2.0" + debug "^4.1.1" + globby "^11.0.1" + is-glob "^4.0.1" + lodash "^4.17.15" + semver "^7.3.2" + tsutils "^3.17.1" + +"@typescript-eslint/visitor-keys@4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.2.0.tgz#ae13838e3a260b63ae51021ecaf1d0cdea8dbba5" + integrity sha512-WIf4BNOlFOH2W+YqGWa6YKLcK/EB3gEj2apCrqLw6mme1RzBy0jtJ9ewJgnrZDB640zfnv8L+/gwGH5sYp/rGw== + dependencies: + "@typescript-eslint/types" "4.2.0" + eslint-visitor-keys "^2.0.0" + +acorn-jsx@^5.2.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" + integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== + +acorn@^7.4.0: + version "7.4.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.0.tgz#e1ad486e6c54501634c6c397c5c121daa383607c" + integrity sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w== + +ajv@^6.10.0, ajv@^6.10.2: + version "6.12.5" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.5.tgz#19b0e8bae8f476e5ba666300387775fb1a00a4da" + integrity sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-colors@3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813" + integrity sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw== + +ansi-escapes@^4.2.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" + integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA== + dependencies: + type-fest "^0.11.0" + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + +ansi-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" + integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" + integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== + dependencies: + "@types/color-name" "^1.1.1" + color-convert "^2.0.1" + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +array-from@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/array-from/-/array-from-2.1.1.tgz#cfe9d8c26628b9dc5aecc62a9f5d8f1f352c1195" + integrity sha1-z+nYwmYoudxa7MYqn12PHzUsEZU= + +array-includes@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.1.tgz#cdd67e6852bdf9c1215460786732255ed2459348" + integrity sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0" + is-string "^1.0.5" + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +array.prototype.flat@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz#0de82b426b0318dbfdb940089e38b043d37f6c7b" + integrity sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + +astral-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" + integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +basic-auth@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a" + integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg== + dependencies: + safe-buffer "5.1.2" + +bluebird@^2.10.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1" + integrity sha1-U0uQM8AiyVecVro7Plpcqvu2UOE= + +bluebird@^3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browser-stdout@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + +builtin-modules@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" + integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^5.0.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.0, chalk@^2.4.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0, chalk@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-width@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" + integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== + +cliui@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" + integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== + dependencies: + string-width "^3.1.0" + strip-ansi "^5.2.0" + wrap-ansi "^5.1.0" + +co-bluebird@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/co-bluebird/-/co-bluebird-1.1.0.tgz#c8b9f3a9320a7ed30987dcca1a5c3cff59655c7c" + integrity sha1-yLnzqTIKftMJh9zKGlw8/1llXHw= + dependencies: + bluebird "^2.10.0" + co-use "^1.1.0" + +co-use@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/co-use/-/co-use-1.1.0.tgz#c6bb3cdf10cb735ecaa9daeeda46d725c94a4e62" + integrity sha1-xrs83xDLc17Kqdru2kbXJclKTmI= + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +commander@^2.12.1: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +contains-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" + integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo= + +cross-spawn@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +cross-spawn@^7.0.2: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@3.2.6: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" + +debug@^2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^4.0.1, debug@^4.1.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1" + integrity sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg== + dependencies: + ms "2.1.2" + +decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + +deep-is@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + +define-properties@^1.1.2, define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +diff@3.5.0, diff@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" + integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +doctrine@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" + integrity sha1-N53Ocw9hZvds76TmcHoVmwLFpvo= + dependencies: + esutils "^2.0.2" + isarray "^1.0.0" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +error-ex@^1.2.0, error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.5: + version "1.17.6" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a" + integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.2.0" + is-regex "^1.1.0" + object-inspect "^1.7.0" + object-keys "^1.1.1" + object.assign "^4.1.0" + string.prototype.trimend "^1.0.1" + string.prototype.trimstart "^1.0.1" + +es-abstract@^1.18.0-next.0: + version "1.18.0-next.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.0.tgz#b302834927e624d8e5837ed48224291f2c66e6fc" + integrity sha512-elZXTZXKn51hUBdJjSZGYRujuzilgXo8vSPQzjGYXLvSlGiCo8VO8ZGV3kjo9a0WNJJ57hENagwbtlRuHuzkcQ== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.2.0" + is-negative-zero "^2.0.0" + is-regex "^1.1.1" + object-inspect "^1.8.0" + object-keys "^1.1.1" + object.assign "^4.1.0" + string.prototype.trimend "^1.0.1" + string.prototype.trimstart "^1.0.1" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +es6-object-assign@^1.0.3: + version "1.1.0" + resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c" + integrity sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw= + +escape-string-regexp@1.0.5, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +eslint-config-prettier@^6.12.0: + version "6.12.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.12.0.tgz#9eb2bccff727db1c52104f0b49e87ea46605a0d2" + integrity sha512-9jWPlFlgNwRUYVoujvWTQ1aMO8o6648r+K7qU7K5Jmkbyqav1fuEZC0COYpGBxyiAJb65Ra9hrmFx19xRGwXWw== + dependencies: + get-stdin "^6.0.0" + +eslint-import-resolver-node@^0.3.3: + version "0.3.4" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717" + integrity sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA== + dependencies: + debug "^2.6.9" + resolve "^1.13.1" + +eslint-module-utils@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz#579ebd094f56af7797d19c9866c9c9486629bfa6" + integrity sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA== + dependencies: + debug "^2.6.9" + pkg-dir "^2.0.0" + +eslint-plugin-import@^2.22.0: + version "2.22.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz#92f7736fe1fde3e2de77623c838dd992ff5ffb7e" + integrity sha512-66Fpf1Ln6aIS5Gr/55ts19eUuoDhAbZgnr6UxK5hbDx6l/QgQgx61AePq+BV4PP2uXQFClgMVzep5zZ94qqsxg== + dependencies: + array-includes "^3.1.1" + array.prototype.flat "^1.2.3" + contains-path "^0.1.0" + debug "^2.6.9" + doctrine "1.5.0" + eslint-import-resolver-node "^0.3.3" + eslint-module-utils "^2.6.0" + has "^1.0.3" + minimatch "^3.0.4" + object.values "^1.1.1" + read-pkg-up "^2.0.0" + resolve "^1.17.0" + tsconfig-paths "^3.9.0" + +eslint-scope@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-utils@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" + integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + +eslint-visitor-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" + integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== + +eslint@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.0.0.tgz#c35dfd04a4372110bd78c69a8d79864273919a08" + integrity sha512-qY1cwdOxMONHJfGqw52UOpZDeqXy8xmD0u8CT6jIstil72jkhURC704W8CFyTPDPllz4z4lu0Ql1+07PG/XdIg== + dependencies: + "@babel/code-frame" "^7.0.0" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.0.1" + doctrine "^3.0.0" + eslint-scope "^5.0.0" + eslint-utils "^2.0.0" + eslint-visitor-keys "^1.1.0" + espree "^7.0.0" + esquery "^1.2.0" + esutils "^2.0.2" + file-entry-cache "^5.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^5.0.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + inquirer "^7.0.0" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash "^4.17.14" + minimatch "^3.0.4" + natural-compare "^1.4.0" + optionator "^0.9.1" + progress "^2.0.0" + regexpp "^3.1.0" + semver "^7.2.1" + strip-ansi "^6.0.0" + strip-json-comments "^3.1.0" + table "^5.2.3" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^7.0.0: + version "7.3.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.0.tgz#dc30437cf67947cf576121ebd780f15eeac72348" + integrity sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw== + dependencies: + acorn "^7.4.0" + acorn-jsx "^5.2.0" + eslint-visitor-keys "^1.3.0" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" + integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" + integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +external-editor@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.1.1: + version "3.2.4" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3" + integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.0" + merge2 "^1.3.0" + micromatch "^4.0.2" + picomatch "^2.2.1" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +fastq@^1.6.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.8.0.tgz#550e1f9f59bbc65fe185cb6a9b4d95357107f481" + integrity sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q== + dependencies: + reusify "^1.0.4" + +figures@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" + integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== + dependencies: + flat-cache "^2.0.1" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-up@3.0.0, find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + +find-up@^2.0.0, find-up@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= + dependencies: + locate-path "^2.0.0" + +flat-cache@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" + integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== + dependencies: + flatted "^2.0.0" + rimraf "2.6.3" + write "1.0.3" + +flat@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/flat/-/flat-4.1.0.tgz#090bec8b05e39cba309747f1d588f04dbaf98db2" + integrity sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw== + dependencies: + is-buffer "~2.0.3" + +flatted@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" + integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + +get-caller-file@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-stdin@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b" + integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g== + +glob-parent@^5.0.0, glob-parent@^5.1.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" + integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== + dependencies: + is-glob "^4.0.1" + +glob@7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== + 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" + +glob@^7.0.0, glob@^7.1.1, glob@^7.1.3: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + 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" + +globals@^12.1.0: + version "12.4.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" + integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== + dependencies: + type-fest "^0.8.1" + +globby@^11.0.1: + version "11.0.1" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357" + integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.0.0" + +graceful-fs@^4.1.2: + version "4.2.4" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" + integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== + +growl@1.10.5: + version "1.10.5" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" + integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.0, has-symbols@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" + integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +he@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +hosted-git-info@^2.1.4: + version "2.8.8" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" + integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== + +iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +ignore@^5.1.4: + version "5.1.8" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" + integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== + +import-fresh@^3.0.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" + integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inquirer@^7.0.0: + version "7.3.3" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" + integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.19" + mute-stream "0.0.8" + run-async "^2.4.0" + rxjs "^6.6.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + +interpret@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" + integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + +is-buffer@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623" + integrity sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A== + +is-callable@^1.1.4, is-callable@^1.2.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" + integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== + +is-date-object@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" + integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-generator/-/is-generator-1.0.3.tgz#c14c21057ed36e328db80347966c693f886389f3" + integrity sha1-wUwhBX7TbjKNuANHlmxpP4hjifM= + +is-glob@^4.0.0, is-glob@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-negative-zero@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461" + integrity sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE= + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-regex@^1.1.0, is-regex@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" + integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg== + dependencies: + has-symbols "^1.0.1" + +is-string@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" + integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== + +is-symbol@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" + integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== + dependencies: + has-symbols "^1.0.1" + +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + +isarray@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@3.13.1: + version "3.13.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^3.13.1: + version "3.14.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" + integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +json-parse-better-errors@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + +json5@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" + integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + dependencies: + minimist "^1.2.0" + +just-extend@^4.0.2: + version "4.1.1" + resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.1.1.tgz#158f1fdb01f128c411dc8b286a7b4837b3545282" + integrity sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +load-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + integrity sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg= + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" + +load-json-file@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" + integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= + dependencies: + graceful-fs "^4.1.2" + parse-json "^4.0.0" + pify "^3.0.0" + strip-bom "^3.0.0" + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + +lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20: + version "4.17.20" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" + integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== + +log-symbols@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" + integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== + dependencies: + chalk "^2.0.1" + +lolex@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lolex/-/lolex-4.2.0.tgz#ddbd7f6213ca1ea5826901ab1222b65d714b3cd7" + integrity sha512-gKO5uExCXvSm6zbF562EvM+rd1kQDnB9AZBbiQVzf1ZmdDpxUSvpnAaVOP83N/31mRK8Ml8/VE8DMvsAZQ+7wg== + +lolex@^5.0.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/lolex/-/lolex-5.1.2.tgz#953694d098ce7c07bc5ed6d0e42bc6c0c6d5a367" + integrity sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A== + dependencies: + "@sinonjs/commons" "^1.7.0" + +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= + +memorystream@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" + integrity sha1-htcJCzDORV1j+64S3aUaR93K+bI= + +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" + integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== + dependencies: + braces "^3.0.1" + picomatch "^2.0.5" + +mime-db@1.44.0: + version "1.44.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" + integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== + +mime-types@~2.1.24: + version "2.1.27" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" + integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== + dependencies: + mime-db "1.44.0" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimatch@3.0.4, minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.0, minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +mkdirp@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.4.tgz#fd01504a6797ec5c9be81ff43d204961ed64a512" + integrity sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw== + dependencies: + minimist "^1.2.5" + +mkdirp@^0.5.1: + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + dependencies: + minimist "^1.2.5" + +mocha@^6.2.2: + version "6.2.3" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-6.2.3.tgz#e648432181d8b99393410212664450a4c1e31912" + integrity sha512-0R/3FvjIGH3eEuG17ccFPk117XL2rWxatr81a57D+r/x2uTYZRbdZ4oVidEUMh2W2TJDa7MdAb12Lm2/qrKajg== + dependencies: + ansi-colors "3.2.3" + browser-stdout "1.3.1" + debug "3.2.6" + diff "3.5.0" + escape-string-regexp "1.0.5" + find-up "3.0.0" + glob "7.1.3" + growl "1.10.5" + he "1.2.0" + js-yaml "3.13.1" + log-symbols "2.2.0" + minimatch "3.0.4" + mkdirp "0.5.4" + ms "2.1.1" + node-environment-flags "1.0.5" + object.assign "4.1.0" + strip-json-comments "2.0.1" + supports-color "6.0.0" + which "1.3.1" + wide-align "1.1.3" + yargs "13.3.2" + yargs-parser "13.1.2" + yargs-unparser "1.6.0" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + +ms@2.1.2, ms@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +mute-stream@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +nise@^1.5.2: + version "1.5.3" + resolved "https://registry.yarnpkg.com/nise/-/nise-1.5.3.tgz#9d2cfe37d44f57317766c6e9408a359c5d3ac1f7" + integrity sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ== + 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" + +node-environment-flags@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.5.tgz#fa930275f5bf5dae188d6192b24b4c8bbac3d76a" + integrity sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ== + dependencies: + object.getownpropertydescriptors "^2.0.3" + semver "^5.7.0" + +normalize-package-data@^2.3.2: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +npm-run-all@^4.1.5: + version "4.1.5" + resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.5.tgz#04476202a15ee0e2e214080861bff12a51d98fba" + integrity sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ== + dependencies: + ansi-styles "^3.2.1" + chalk "^2.4.1" + cross-spawn "^6.0.5" + memorystream "^0.3.1" + minimatch "^3.0.4" + pidtree "^0.3.0" + read-pkg "^3.0.0" + shell-quote "^1.6.1" + string.prototype.padend "^3.0.0" + +object-inspect@^1.7.0, object-inspect@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" + integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== + +object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + has-symbols "^1.0.0" + object-keys "^1.0.11" + +object.assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.1.tgz#303867a666cdd41936ecdedfb1f8f3e32a478cdd" + integrity sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.18.0-next.0" + has-symbols "^1.0.1" + object-keys "^1.1.1" + +object.getownpropertydescriptors@^2.0.3: + version "2.1.0" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" + integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + +object.values@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" + integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + has "^1.0.3" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +onetime@^5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + dependencies: + 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" + +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +p-limit@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" + integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== + dependencies: + p-try "^1.0.0" + +p-limit@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= + dependencies: + p-limit "^1.1.0" + +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck= + dependencies: + error-ex "^1.2.0" + +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + +path-to-regexp@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" + integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== + dependencies: + isarray "0.0.1" + +path-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + integrity sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM= + dependencies: + pify "^2.0.0" + +path-type@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" + integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== + dependencies: + pify "^3.0.0" + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picomatch@^2.0.5, picomatch@^2.2.1: + version "2.2.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" + integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== + +pidtree@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.1.tgz#ef09ac2cc0533df1f3250ccf2c4d366b0d12114a" + integrity sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA== + +pify@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= + +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= + +pkg-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" + integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s= + dependencies: + find-up "^2.1.0" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +promisify-any@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/promisify-any/-/promisify-any-2.0.1.tgz#403e00a8813f175242ab50fe33a69f8eece47305" + integrity sha1-QD4AqIE/F1JCq1D+M6afjuzkcwU= + dependencies: + bluebird "^2.10.0" + co-bluebird "^1.1.0" + is-generator "^1.0.2" + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +read-pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + integrity sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4= + dependencies: + find-up "^2.0.0" + read-pkg "^2.0.0" + +read-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + integrity sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg= + dependencies: + load-json-file "^2.0.0" + normalize-package-data "^2.3.2" + path-type "^2.0.0" + +read-pkg@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" + integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k= + dependencies: + load-json-file "^4.0.0" + normalize-package-data "^2.3.2" + path-type "^3.0.0" + +rechoir@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" + integrity sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q= + dependencies: + resolve "^1.1.6" + +regexpp@^3.0.0, regexpp@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" + integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve@^1.1.6, resolve@^1.10.0, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.3.2: + version "1.17.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" + integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== + dependencies: + path-parse "^1.0.6" + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@2.6.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + +run-async@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== + +run-parallel@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" + integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== + +rxjs@^6.6.0: + version "6.6.3" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.3.tgz#8ca84635c4daa900c0d3967a6ee7ac60271ee552" + integrity sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ== + dependencies: + tslib "^1.9.0" + +safe-buffer@5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0, semver@^5.7.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^7.2.1, semver@^7.3.2: + version "7.3.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" + integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shell-quote@^1.6.1: + version "1.7.2" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" + integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== + +shelljs@^0.8.1: + version "0.8.4" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.4.tgz#de7684feeb767f8716b326078a8a00875890e3c2" + integrity sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ== + dependencies: + glob "^7.0.0" + interpret "^1.0.0" + rechoir "^0.6.2" + +should-equal@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/should-equal/-/should-equal-2.0.0.tgz#6072cf83047360867e68e98b09d71143d04ee0c3" + integrity sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA== + dependencies: + should-type "^1.4.0" + +should-format@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/should-format/-/should-format-3.0.3.tgz#9bfc8f74fa39205c53d38c34d717303e277124f1" + integrity sha1-m/yPdPo5IFxT04w01xcwPidxJPE= + dependencies: + should-type "^1.3.0" + should-type-adaptors "^1.0.1" + +should-type-adaptors@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz#401e7f33b5533033944d5cd8bf2b65027792e27a" + integrity sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA== + dependencies: + should-type "^1.3.0" + should-util "^1.0.0" + +should-type@^1.3.0, should-type@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/should-type/-/should-type-1.4.0.tgz#0756d8ce846dfd09843a6947719dfa0d4cff5cf3" + integrity sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM= + +should-util@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/should-util/-/should-util-1.0.1.tgz#fb0d71338f532a3a149213639e2d32cbea8bcb28" + integrity sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g== + +should@^13.2.3: + version "13.2.3" + resolved "https://registry.yarnpkg.com/should/-/should-13.2.3.tgz#96d8e5acf3e97b49d89b51feaa5ae8d07ef58f10" + integrity sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ== + 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" + +shx@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/shx/-/shx-0.3.2.tgz#40501ce14eb5e0cbcac7ddbd4b325563aad8c123" + integrity sha512-aS0mWtW3T2sHAenrSrip2XGv39O9dXIFUqxAEWHEOS1ePtGIBavdPJY1kE2IHl14V/4iCbUiNDPGdyYTtmhSoA== + dependencies: + es6-object-assign "^1.0.3" + minimist "^1.2.0" + shelljs "^0.8.1" + +signal-exit@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" + integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== + +sinon@^7.5.0: + version "7.5.0" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-7.5.0.tgz#e9488ea466070ea908fd44a3d6478fd4923c67ec" + integrity sha512-AoD0oJWerp0/rY9czP/D6hDTTUYGpObhZjMpd7Cl/A6+j0xBE+ayL/ldfggkBXUs0IkvIiM1ljM8+WkOc5k78Q== + 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" + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +slice-ansi@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" + integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== + dependencies: + ansi-styles "^3.2.0" + astral-regex "^1.0.0" + is-fullwidth-code-point "^2.0.0" + +source-map-support@^0.5.17: + version "0.5.19" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" + integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +spdx-correct@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" + integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.6" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz#c80757383c28abf7296744998cbc106ae8b854ce" + integrity sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +statuses@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.0.tgz#aa7b107e018eb33e08e8aee2e7337e762dda1028" + integrity sha512-w9jNUUQdpuVoYqXxnyOakhckBbOxRaoYqJscyIBYCS5ixyCnO7nQn7zBZvP9zf5QOPZcz2DLUpE3KsNPbJBOFA== + +"string-width@^1.0.2 || 2": + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string-width@^3.0.0, string-width@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +string-width@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" + integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.0" + +string.prototype.padend@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.1.0.tgz#dc08f57a8010dc5c153550318f67e13adbb72ac3" + integrity sha512-3aIv8Ffdp8EZj8iLwREGpQaUZiPyrWrpzMBHvkiSW/bK/EGve9np07Vwy7IJ5waydpGXzQZu/F8Oze2/IWkBaA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + +string.prototype.trimend@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" + integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + +string.prototype.trimstart@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" + integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-ansi@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" + integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== + dependencies: + ansi-regex "^5.0.0" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= + +strip-json-comments@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + +strip-json-comments@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.0.0.tgz#76cfe742cf1f41bb9b1c29ad03068c05b4c0e40a" + integrity sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg== + dependencies: + has-flag "^3.0.0" + +supports-color@^5.3.0, supports-color@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +table@^5.2.3: + version "5.4.6" + resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" + integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== + dependencies: + ajv "^6.10.2" + lodash "^4.17.14" + slice-ansi "^2.1.0" + string-width "^3.0.0" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + +through@^2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +ts-node@^8.5.4: + version "8.10.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.10.2.tgz#eee03764633b1234ddd37f8db9ec10b75ec7fb8d" + integrity sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA== + dependencies: + arg "^4.1.0" + diff "^4.0.1" + make-error "^1.1.1" + source-map-support "^0.5.17" + yn "3.1.1" + +tsconfig-paths@^3.9.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b" + integrity sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.1" + minimist "^1.2.0" + strip-bom "^3.0.0" + +tslib@^1.10.0, tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" + integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== + +tslint@^5.20.1: + version "5.20.1" + resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.20.1.tgz#e401e8aeda0152bc44dd07e614034f3f80c67b7d" + integrity sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg== + dependencies: + "@babel/code-frame" "^7.0.0" + builtin-modules "^1.1.1" + chalk "^2.3.0" + commander "^2.12.1" + diff "^4.0.1" + glob "^7.1.1" + js-yaml "^3.13.1" + minimatch "^3.0.4" + mkdirp "^0.5.1" + resolve "^1.3.2" + semver "^5.3.0" + tslib "^1.8.0" + tsutils "^2.29.0" + +tsutils@^2.29.0: + version "2.29.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" + integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== + dependencies: + tslib "^1.8.1" + +tsutils@^3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" + integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g== + dependencies: + tslib "^1.8.1" + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" + integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ== + +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +type-is@^1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +typescript@^3.7.2: + version "3.9.7" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" + integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== + +uri-js@^4.2.2: + version "4.4.0" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.0.tgz#aa714261de793e8a82347a7bcc9ce74e86f28602" + integrity sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g== + dependencies: + punycode "^2.1.0" + +v8-compile-cache@^2.0.3: + version "2.1.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745" + integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ== + +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= + +which@1.3.1, which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wide-align@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== + dependencies: + string-width "^1.0.2 || 2" + +word-wrap@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wrap-ansi@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" + integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== + dependencies: + ansi-styles "^3.2.0" + string-width "^3.0.0" + strip-ansi "^5.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +write@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" + integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== + dependencies: + mkdirp "^0.5.1" + +y18n@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" + integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== + +yargs-parser@13.1.2, yargs-parser@^13.1.2: + version "13.1.2" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" + integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs-unparser@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz#ef25c2c769ff6bd09e4b0f9d7c605fb27846ea9f" + integrity sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw== + dependencies: + flat "^4.1.0" + lodash "^4.17.15" + yargs "^13.3.0" + +yargs@13.3.2, yargs@^13.3.0: + version "13.3.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" + integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== + dependencies: + cliui "^5.0.0" + find-up "^3.0.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 "^3.0.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^13.1.2" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==