├── .github ├── FUNDING.yml └── dependabot.yml ├── .gitignore ├── .jshintignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── docs ├── Makefile ├── _static │ ├── custom.css │ └── favicon.ico ├── api │ ├── errors │ │ ├── access-denied-error.rst │ │ ├── index.rst │ │ ├── insufficient-scope-error.rst │ │ ├── invalid-argument-error.rst │ │ ├── invalid-client-error.rst │ │ ├── invalid-grant-error.rst │ │ ├── invalid-request-error.rst │ │ ├── invalid-scope-error.rst │ │ ├── invalid-token-error.rst │ │ ├── oauth-error.rst │ │ ├── server-error.rst │ │ ├── unauthorized-client-error.rst │ │ ├── unauthorized-request-error.rst │ │ ├── unsupported-grant-type-error.rst │ │ └── unsupported-response-type-error.rst │ ├── oauth2-server.rst │ ├── request.rst │ └── response.rst ├── conf.py ├── docs │ ├── adapters.rst │ └── getting-started.rst ├── index.rst ├── make.bat ├── misc │ ├── extension-grants.rst │ └── migrating-v2-to-v3.rst ├── model │ ├── overview.rst │ └── spec.rst └── npm_conf.py ├── index.js ├── lib ├── errors │ ├── access-denied-error.js │ ├── insufficient-scope-error.js │ ├── invalid-argument-error.js │ ├── invalid-client-error.js │ ├── invalid-grant-error.js │ ├── invalid-request-error.js │ ├── invalid-scope-error.js │ ├── invalid-token-error.js │ ├── oauth-error.js │ ├── server-error.js │ ├── unauthorized-client-error.js │ ├── unauthorized-request-error.js │ ├── unsupported-grant-type-error.js │ └── unsupported-response-type-error.js ├── grant-types │ ├── abstract-grant-type.js │ ├── authorization-code-grant-type.js │ ├── client-credentials-grant-type.js │ ├── password-grant-type.js │ └── refresh-token-grant-type.js ├── handlers │ ├── authenticate-handler.js │ ├── authorize-handler.js │ └── token-handler.js ├── models │ └── token-model.js ├── request.js ├── response-types │ ├── code-response-type.js │ └── token-response-type.js ├── response.js ├── server.js ├── token-types │ ├── bearer-token-type.js │ └── mac-token-type.js ├── utils │ └── token-util.js └── validator │ └── is.js ├── package-lock.json ├── package.json └── test ├── assertions.js ├── integration ├── grant-types │ ├── abstract-grant-type_test.js │ ├── authorization-code-grant-type_test.js │ ├── client-credentials-grant-type_test.js │ ├── password-grant-type_test.js │ └── refresh-token-grant-type_test.js ├── handlers │ ├── authenticate-handler_test.js │ ├── authorize-handler_test.js │ └── token-handler_test.js ├── request_test.js ├── response-types │ └── code-response-type_test.js ├── response_test.js ├── server_test.js ├── token-types │ └── bearer-token-type_test.js └── utils │ └── token-util_test.js ├── mocha.opts └── unit ├── grant-types ├── abstract-grant-type_test.js ├── authorization-code-grant-type_test.js ├── client-credentials-grant-type_test.js ├── password-grant-type_test.js └── refresh-token-grant-type_test.js ├── handlers ├── authenticate-handler_test.js ├── authorize-handler_test.js └── token-handler_test.js ├── models └── token-model_test.js ├── request_test.js ├── response_test.js └── server_test.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: thomseddon 2 | 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | ignore: 9 | - dependency-name: sinon 10 | versions: 11 | - 10.0.0 12 | - 9.2.4 13 | - dependency-name: mocha 14 | versions: 15 | - 8.2.1 16 | - 8.3.0 17 | - 8.3.1 18 | - dependency-name: lodash 19 | versions: 20 | - 4.17.20 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | docs/_build/ 3 | __pycache__/ 4 | *.pyc 5 | lib-cov 6 | *.seed 7 | *.log 8 | *.csv 9 | *.dat 10 | *.out 11 | *.pid 12 | *.gz 13 | *.iml 14 | 15 | .idea 16 | .jshint 17 | .DS_Store 18 | 19 | pids 20 | logs 21 | results 22 | 23 | lib/dockerImage/keys 24 | coverage 25 | npm-debug.log*~ 26 | \#*\# 27 | /.emacs.desktop 28 | /.emacs.desktop.lock 29 | .elc 30 | auto-save-list 31 | tramp 32 | .\#* 33 | 34 | # Org-mode 35 | .org-id-locations 36 | *_archive 37 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "curly": true, 4 | "eqeqeq": true, 5 | "esnext": true, 6 | "expr": true, 7 | "globalstrict": false, 8 | "immed": true, 9 | "indent": 2, 10 | "jquery": true, 11 | "latedef": false, 12 | "mocha": true, 13 | "newcap": true, 14 | "noarg": true, 15 | "node": true, 16 | "noyield": true, 17 | "predef": ["-Promise"], 18 | "quotmark": "single", 19 | "regexp": true, 20 | "smarttabs": true, 21 | "strict": false, 22 | "trailing": false, 23 | "undef": true, 24 | "unused": true, 25 | "white": false 26 | } 27 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - 4 5 | - 6 6 | - 8 7 | - 10 8 | - 12 9 | - 13 10 | - 14 11 | 12 | sudo: false 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Changelog 2 | 3 | ### 4.0.0 4 | * Bump jshint from 2.12.0 to 2.13.0 5 | * Bump jshint from 2.12.0 to 2.13.0 6 | * Upgrade to GitHub-native Dependabot 7 | * [Security] Bump lodash from 4.17.19 to 4.17.21 8 | 9 | ### 3.1.0 10 | * new: .npmignore tests 11 | * fix: validate requested scope on authorize request 12 | * fix: always issue correct expiry dates for tokens 13 | * fix: set numArgs for promisify of generateAuthorizationCode 14 | * fix: Changed 'hasOwnProperty' call in Response 15 | * docs: Ensure accessTokenExpiresAt is required 16 | * docs: Add missing notice of breaking change for accessExpireLifetime to migration guide 17 | * docs: Correct tokens time scale for 2.x to 3.x migration guide 18 | * readme: Update Slack badge and link 19 | * readme: Fix link to RFC6750 standard 20 | 21 | ### 3.0.2 (24/05/2020) 22 | 23 | * Update all dependencies 🎉 24 | 25 | ### 3.0.1 (27/08/2018) 26 | 27 | * Doc fixes 28 | 29 | Tag never released on npm 30 | 31 | ### 3.0.0 (04/08/2017) 32 | * Complete re-write, with Promises and callback support 33 | * Dropped support for node v0.8, v0.10, v0.12 34 | * Supports Node v4, v6, v7, and v8. Will continue support for node current and active LTS versions 35 | * For migration guide, see https://oauth2-server.readthedocs.io/en/latest/misc/migrating-v2-to-v3.html 36 | 37 | ### 2.4.1 38 | 39 | - Fix header setting syntax 40 | - Fix docs for supported grant types 41 | 42 | ### 2.4.0 43 | 44 | - Set Cache-Control and Pragma headers 45 | - Allow any valid URI for extension grants 46 | - Expose `client` to `extendedGrant` and after via `req.oauth.client` 47 | - Fix express depreciation warning for `res.send()` 48 | - Expose `user` to `generateToken` and after via `req.user` 49 | - Fix lockdown pattern for express 3 50 | 51 | - Add redis example 52 | - Fix docs to use new express bodyParser module 53 | - Fix docs for `redirect_uri` 54 | - Clarify docs for `clientIdRegex` 55 | - Fix docs for missing `req` argument in `generateToken` 56 | - Fix docs for `user`/`userId` `getAccessToken` 57 | - Fix docs for argument order in `getRefreshToken` 58 | 59 | ### 2.3.0 60 | 61 | - Support "state" param for auth_code grant type 62 | - Docs for client_credentials grant type 63 | - Fix `getRefreshToken` in postgres model example 64 | 65 | ### 2.2.2 66 | 67 | - Fix bug when client has multiple redirect_uri's (#84) 68 | 69 | ### 2.2.1 70 | 71 | - Fix node 0.8.x (well npm 1.2.x) support 72 | 73 | ### 2.2.0 74 | 75 | - Support custom loggers via `debug` param 76 | - Make OAuth2Error inherit from Error for fun and profit 77 | - Don't go crazy when body is `null` 78 | - Update tests and examples to express 4 79 | - Fix lockdown pattern for express 4 80 | - Update dev dependencies (mocha, should and supertest) 81 | 82 | ### 2.1.1 83 | 84 | - Allow client to return an array of multiple valid redirect URI's 85 | - Fix continueAfterResponse when granting 86 | 87 | ### 2.1.0 88 | - Add support for client_credentials grant type (@lucknerjb) 89 | - Support Authorization grant via GET request (@mjsalinger) 90 | 91 | ### 2.0.2 92 | - Fix continueAfterResponse option 93 | 94 | ### 2.0.1 95 | - Add "WWW-Authenticate" header for invalid_client 96 | 97 | ### 2.0 98 | - Huge intrenal refactor 99 | - Switch from internal router ("allow" property) to exposing explit authorisation middleware to be added to individual routes 100 | - Expose grant middleware to be attached to a route of your choosing 101 | - Switch all model variables to camelCasing 102 | - Add support for `authorization_code` grant type (i.e. traditional "allow", "deny" with redirects etc.) 103 | - Some, previously wrong, error codes fixed 104 | 105 | ### 1.5.3 106 | - Fix tests for daylight saving 107 | 108 | ### 1.5.2 109 | - Fix expiration token checking (previously expires was wrongly checked against boot time) 110 | 111 | ### 1.5.1 112 | - Add repository field to package 113 | 114 | ### 1.5.0 115 | - Add support for non-expiring tokens (set accessTokenLifetime/refreshTokenLifetime = null) 116 | - Passthrough debug errors from custom generateToken 117 | 118 | ### 1.4.1 119 | - Allow access token in body when not POST (only deny GET) 120 | 121 | ### 1.4.0 122 | - Add support for refresh_token grant type 123 | 124 | ### 1.3.2 125 | - Require application/x-www-form-urlencoded when access token in body 126 | - Require authentication on both client id and secret 127 | 128 | ### 1.3.1 129 | - Fix client credentials extraction from Authorization header 130 | 131 | ### 1.3.0 132 | - Add passthroughErrors option 133 | - Optimise oauth.handler() with regex caching 134 | - Add PostgreSQL example 135 | - Allow req.user to be set by setting token.user in getAccessToken 136 | 137 | ### 1.2.5 138 | - Expose the token passed back from getAccessToken in req.token 139 | 140 | ### 1.2.4 141 | - Pass through Bad Request errors from connect 142 | 143 | ### 1.2.3 144 | - Fix generateToken override 145 | - Allow extended grant to pass back custom error 146 | 147 | ### 1.2.2 148 | - Fix reissuing 149 | 150 | ### 1.2.1 151 | - Allow token reissuing (Model can return an object to indicate a reissue, plain string (as in previous implementation) or null to revert to the default token generator) 152 | 153 | ### 1.2.0 154 | - Add optional generateToken method to model to allow custom token generation 155 | 156 | ### 1.1.1 157 | - Fix expired token checking 158 | 159 | ### 1.1.0 160 | - Add support for extension grants 161 | - Use async crypto.randomBytes in token generation 162 | - Refactor structure, break into more files 163 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 oauthJs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # oauth2-server 3 | 4 | [![npm Version][npm-image]][npm-url] 5 | [![npm Downloads][downloads-image]][downloads-url] 6 | [![Test Status][travis-image]][travis-url] 7 | [![MIT Licensed][license-image]][license-url] 8 | [![oauthjs Slack][slack-image]][slack-url] 9 | 10 | Complete, compliant and well tested module for implementing an OAuth2 server in [Node.js](https://nodejs.org). 11 | 12 | Note: After a period of hiatus, this project is now back under active maintenance. Dependencies have been updated and bug fixes will land in v3 (current master). v4 will be _mostly backwards compatible_ with no code changes required for users using a supported node release. More details in [#621](https://github.com/oauthjs/node-oauth2-server/issues/621). 13 | 14 | ## Installation 15 | 16 | ```bash 17 | npm install oauth2-server 18 | ``` 19 | 20 | The *oauth2-server* module is framework-agnostic but there are several officially supported wrappers available for popular HTTP server frameworks such as [Express](https://npmjs.org/package/express-oauth-server) and [Koa](https://npmjs.org/package/koa-oauth-server). If you're using one of those frameworks it is strongly recommended to use the respective wrapper module instead of rolling your own. 21 | 22 | 23 | ## Features 24 | 25 | - Supports `authorization_code`, `client_credentials`, `refresh_token` and `password` grant, as well as *extension grants*, with scopes. 26 | - Can be used with *promises*, *Node-style callbacks*, *ES6 generators* and *async*/*await* (using [Babel](https://babeljs.io)). 27 | - Fully [RFC 6749](https://tools.ietf.org/html/rfc6749.html) and [RFC 6750](https://tools.ietf.org/html/rfc6750.html) compliant. 28 | - Implicitly supports any form of storage, e.g. *PostgreSQL*, *MySQL*, *MongoDB*, *Redis*, etc. 29 | - Complete [test suite](https://github.com/oauthjs/node-oauth2-server/tree/master/test). 30 | 31 | 32 | ## Documentation 33 | 34 | [Documentation](https://oauth2-server.readthedocs.io) is hosted on Read the Docs. 35 | 36 | 37 | ## Examples 38 | 39 | Most users should refer to our [Express](https://github.com/oauthjs/express-oauth-server/tree/master/examples) or [Koa](https://github.com/oauthjs/koa-oauth-server/tree/master/examples) examples. 40 | 41 | More examples can be found here: https://github.com/14gasher/oauth-example 42 | 43 | ## Upgrading from 2.x 44 | 45 | This module has been rewritten using a promise-based approach, introducing changes to the API and model specification. v2.x is no longer supported. 46 | 47 | Please refer to our [3.0 migration guide](https://oauth2-server.readthedocs.io/en/latest/misc/migrating-v2-to-v3.html) for more information. 48 | 49 | 50 | ## Tests 51 | 52 | To run the test suite, install dependencies, then run `npm test`: 53 | 54 | ```bash 55 | npm install 56 | npm test 57 | ``` 58 | 59 | 60 | [npm-image]: https://img.shields.io/npm/v/oauth2-server.svg 61 | [npm-url]: https://npmjs.org/package/oauth2-server 62 | [downloads-image]: https://img.shields.io/npm/dm/oauth2-server.svg 63 | [downloads-url]: https://npmjs.org/package/oauth2-server 64 | [travis-image]: https://img.shields.io/travis/oauthjs/node-oauth2-server/master.svg 65 | [travis-url]: https://travis-ci.org/oauthjs/node-oauth2-server 66 | [license-image]: https://img.shields.io/badge/license-MIT-blue.svg 67 | [license-url]: https://raw.githubusercontent.com/oauthjs/node-oauth2-server/master/LICENSE 68 | [slack-image]: https://slack.oauthjs.org/badge.svg 69 | [slack-url]: https://slack.oauthjs.org 70 | 71 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/oauth2-server.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/oauth2-server.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/oauth2-server" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/oauth2-server" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /docs/_static/custom.css: -------------------------------------------------------------------------------- 1 | 2 | /* fix word-wrap for responsive tables, as described here: 3 | * http://rackerlabs.github.io/docs-rackspace/tools/rtd-tables.html */ 4 | @media screen and (min-width: 767px) { 5 | .wy-table-responsive table td { 6 | white-space: normal !important; 7 | } 8 | .wy-table-responsive { 9 | overflow: visible !important; 10 | } 11 | } 12 | 13 | /* ensure that smaller tables span the whole page width */ 14 | .rst-content table.docutils { 15 | width: 100% !important; 16 | } 17 | 18 | /* "Name" column of "arguments" tables */ 19 | .rst-content table.docutils th:nth-child(1), 20 | .rst-content table.docutils td:nth-child(1) { 21 | width: 35% !important; 22 | word-break: break-all !important; 23 | } 24 | 25 | /* "Type" column of "arguments" tables */ 26 | .rst-content table.docutils th:nth-child(2), 27 | .rst-content table.docutils td:nth-child(2) { 28 | width: 20% !important; 29 | word-break: normal !important; 30 | } 31 | 32 | /* "Description" column of "arguments" tables */ 33 | /*.rst-content table.docutils th:nth-child(3), 34 | .rst-content table.docutils td:nth-child(3) { 35 | }*/ 36 | 37 | /* use a slightly smaller font size for table contents */ 38 | .rst-content table.docutils th, 39 | .rst-content table.docutils td { 40 | font-size: 85% !important; 41 | } 42 | 43 | /* reduce left/right padding of literals from 5px to 3px */ 44 | .rst-content code.literal { 45 | padding-left: 3px !important; 46 | padding-right: 3px !important; 47 | } 48 | 49 | /* reset font-size of literals inside the term definition (
) in description lists */ 50 | .rst-content dl dt code.literal { 51 | font-size: 100% !important; 52 | } 53 | 54 | /* external links generated by the :rfc: role are surrounded by 55 | * tags which doesn't look good in floating text */ 56 | .rst-content a.rfc strong { 57 | font-weight: normal !important; 58 | } 59 | 60 | /* default style for blockquotes is just indentation; 61 | * disable indentation and instead use custom background color */ 62 | .rst-content blockquote { 63 | margin-left: 0 !important; 64 | padding: 10px !important; 65 | background-color: #fff8dc !important; 66 | border-left: 2px solid #ffeb8e !important; 67 | } 68 | 69 | -------------------------------------------------------------------------------- /docs/_static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oauthjs/node-oauth2-server/6d4c98794bc024a8cf93cf9e79f92f93766da9f4/docs/_static/favicon.ico -------------------------------------------------------------------------------- /docs/api/errors/access-denied-error.rst: -------------------------------------------------------------------------------- 1 | =================== 2 | AccessDeniedError 3 | =================== 4 | 5 | The resource owner or authorization server denied the request. See :rfc:`Section 4.1.2.1 of RFC 6749 <6749#section-4.1.2.1>`. 6 | 7 | :: 8 | 9 | const AccessDeniedError = require('oauth2-server/lib/errors/access-denied-error'); 10 | 11 | -------- 12 | 13 | .. _AccessDeniedError#constructor: 14 | 15 | ``new AccessDeniedError(message, properties)`` 16 | ============================================== 17 | 18 | Instantiates an ``AccessDeniedError``. 19 | 20 | **Arguments:** 21 | 22 | +-----------------------------------+--------------+-------------------------------------------------------------+ 23 | | Name | Type | Description | 24 | +===================================+==============+=============================================================+ 25 | | [message=undefined] | String|Error | See :ref:`OAuthError#constructor`. | 26 | +-----------------------------------+--------------+-------------------------------------------------------------+ 27 | | [properties={}] | Object | See :ref:`OAuthError#constructor`. | 28 | +-----------------------------------+--------------+-------------------------------------------------------------+ 29 | | [properties.code=400] | Object | See :ref:`OAuthError#constructor`. | 30 | +-----------------------------------+--------------+-------------------------------------------------------------+ 31 | | [properties.name='access_denied'] | String | The error name used in responses generated from this error. | 32 | +-----------------------------------+--------------+-------------------------------------------------------------+ 33 | 34 | **Return value:** 35 | 36 | A new instance of ``AccessDeniedError``. 37 | 38 | **Remarks:** 39 | 40 | :: 41 | 42 | const err = new AccessDeniedError(); 43 | // err.message === 'Bad Request' 44 | // err.code === 400 45 | // err.name === 'access_denied' 46 | 47 | -------- 48 | 49 | .. _AccessDeniedError#message: 50 | 51 | ``message`` 52 | =========== 53 | 54 | See :ref:`OAuthError#message `. 55 | 56 | -------- 57 | 58 | .. _AccessDeniedError#code: 59 | 60 | ``code`` 61 | ======== 62 | 63 | Typically ``400``. See :ref:`OAuthError#code `. 64 | 65 | -------- 66 | 67 | .. _AccessDeniedError#inner: 68 | 69 | ``inner`` 70 | ========= 71 | 72 | See :ref:`OAuthError#inner `. 73 | 74 | -------- 75 | 76 | .. _AccessDeniedError#name: 77 | 78 | ``name`` 79 | ======== 80 | 81 | Typically ``'access_denied'``. See :ref:`OAuthError#name `. 82 | 83 | -------------------------------------------------------------------------------- /docs/api/errors/index.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | Errors 3 | ======== 4 | 5 | Noteable error types: 6 | 7 | ``OAuthError`` 8 | :doc:`oauth-error` is the base class for all exceptions thrown/returned by *oauth2-server*. 9 | 10 | ``ServerError`` 11 | :doc:`server-error` is used to wrap unknown exceptions encountered during request processing. 12 | 13 | ``InvalidArgumentError`` 14 | :doc:`invalid-argument-error` is thrown when an invalid argument is encountered. This error indicates that the module is used incorrectly and should never be seen because of external errors. 15 | 16 | 17 | .. toctree:: 18 | :maxdepth: 1 19 | :caption: Errors 20 | :hidden: 21 | 22 | oauth-error 23 | server-error 24 | invalid-argument-error 25 | access-denied-error 26 | insufficient-scope-error 27 | invalid-client-error 28 | invalid-grant-error 29 | invalid-request-error 30 | invalid-scope-error 31 | invalid-token-error 32 | unauthorized-client-error 33 | unauthorized-request-error 34 | unsupported-grant-type-error 35 | unsupported-response-type-error 36 | 37 | -------------------------------------------------------------------------------- /docs/api/errors/insufficient-scope-error.rst: -------------------------------------------------------------------------------- 1 | ======================== 2 | InsufficientScopeError 3 | ======================== 4 | 5 | The request requires higher privileges than provided by the access token. See :rfc:`Section 3.1 of RFC 6750 <6750#section-3.1>`. 6 | 7 | :: 8 | 9 | const InsufficientScopeError = require('oauth2-server/lib/errors/insufficient-scope-error'); 10 | 11 | -------- 12 | 13 | .. _InsufficientScopeError#constructor: 14 | 15 | ``new InsufficientScopeError(message, properties)`` 16 | =================================================== 17 | 18 | Instantiates an ``InsufficientScopeError``. 19 | 20 | **Arguments:** 21 | 22 | +----------------------------------------+--------------+-------------------------------------------------------------+ 23 | | Name | Type | Description | 24 | +========================================+==============+=============================================================+ 25 | | [message=undefined] | String|Error | See :ref:`OAuthError#constructor`. | 26 | +----------------------------------------+--------------+-------------------------------------------------------------+ 27 | | [properties={}] | Object | See :ref:`OAuthError#constructor`. | 28 | +----------------------------------------+--------------+-------------------------------------------------------------+ 29 | | [properties.code=403] | Object | See :ref:`OAuthError#constructor`. | 30 | +----------------------------------------+--------------+-------------------------------------------------------------+ 31 | | [properties.name='insufficient_scope'] | String | The error name used in responses generated from this error. | 32 | +----------------------------------------+--------------+-------------------------------------------------------------+ 33 | 34 | **Return value:** 35 | 36 | A new instance of ``InsufficientScopeError``. 37 | 38 | **Remarks:** 39 | 40 | :: 41 | 42 | const err = new InsufficientScopeError(); 43 | // err.message === 'Forbidden' 44 | // err.code === 403 45 | // err.name === 'insufficient_scope' 46 | 47 | -------- 48 | 49 | .. _InsufficientScopeError#message: 50 | 51 | ``message`` 52 | =========== 53 | 54 | See :ref:`OAuthError#message `. 55 | 56 | -------- 57 | 58 | .. _InsufficientScopeError#code: 59 | 60 | ``code`` 61 | ======== 62 | 63 | Typically ``403``. See :ref:`OAuthError#code `. 64 | 65 | -------- 66 | 67 | .. _InsufficientScopeError#inner: 68 | 69 | ``inner`` 70 | ========= 71 | 72 | See :ref:`OAuthError#inner `. 73 | 74 | -------- 75 | 76 | .. _InsufficientScopeError#name: 77 | 78 | ``name`` 79 | ======== 80 | 81 | Typically ``'insufficient_scope'``. See :ref:`OAuthError#name `. 82 | 83 | -------------------------------------------------------------------------------- /docs/api/errors/invalid-argument-error.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | InvalidArgumentError 3 | ====================== 4 | 5 | An invalid argument was encountered. 6 | 7 | :: 8 | 9 | const InvalidArgumentError = require('oauth2-server/lib/errors/invalid-argument-error'); 10 | 11 | .. note:: This error indicates that the module is used incorrectly (i.e., there is a programming error) and should never be seen because of external errors (like invalid data sent by a client). 12 | 13 | -------- 14 | 15 | .. _InvalidArgumentError#constructor: 16 | 17 | ``new InvalidArgumentError(message, properties)`` 18 | ================================================= 19 | 20 | Instantiates an ``InvalidArgumentError``. 21 | 22 | **Arguments:** 23 | 24 | +--------------------------------------+--------------+-------------------------------------------------------------+ 25 | | Name | Type | Description | 26 | +======================================+==============+=============================================================+ 27 | | [message=undefined] | String|Error | See :ref:`OAuthError#constructor`. | 28 | +--------------------------------------+--------------+-------------------------------------------------------------+ 29 | | [properties={}] | Object | See :ref:`OAuthError#constructor`. | 30 | +--------------------------------------+--------------+-------------------------------------------------------------+ 31 | | [properties.code=500] | Object | See :ref:`OAuthError#constructor`. | 32 | +--------------------------------------+--------------+-------------------------------------------------------------+ 33 | | [properties.name='invalid_argument'] | String | The error name used in responses generated from this error. | 34 | +--------------------------------------+--------------+-------------------------------------------------------------+ 35 | 36 | **Return value:** 37 | 38 | A new instance of ``InvalidArgumentError``. 39 | 40 | **Remarks:** 41 | 42 | :: 43 | 44 | const err = new InvalidArgumentError(); 45 | // err.message === 'Internal Server Error' 46 | // err.code === 500 47 | // err.name === 'invalid_argument' 48 | 49 | -------- 50 | 51 | .. _InvalidArgumentError#message: 52 | 53 | ``message`` 54 | =========== 55 | 56 | See :ref:`OAuthError#message `. 57 | 58 | -------- 59 | 60 | .. _InvalidArgumentError#code: 61 | 62 | ``code`` 63 | ======== 64 | 65 | Typically ``500``. See :ref:`OAuthError#code `. 66 | 67 | -------- 68 | 69 | .. _InvalidArgumentError#inner: 70 | 71 | ``inner`` 72 | ========= 73 | 74 | See :ref:`OAuthError#inner `. 75 | 76 | -------- 77 | 78 | .. _InvalidArgumentError#name: 79 | 80 | ``name`` 81 | ======== 82 | 83 | Typically ``'invalid_argument'``. See :ref:`OAuthError#name `. 84 | 85 | -------------------------------------------------------------------------------- /docs/api/errors/invalid-client-error.rst: -------------------------------------------------------------------------------- 1 | ==================== 2 | InvalidClientError 3 | ==================== 4 | 5 | Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method). See :rfc:`Section 5.2 of RFC 6749 <6749#section-5.2>`. 6 | 7 | :: 8 | 9 | const InvalidClientError = require('oauth2-server/lib/errors/invalid-client-error'); 10 | 11 | -------- 12 | 13 | .. _InvalidClientError#constructor: 14 | 15 | ``new InvalidClientError(message, properties)`` 16 | =============================================== 17 | 18 | Instantiates an ``InvalidClientError``. 19 | 20 | **Arguments:** 21 | 22 | +------------------------------------+--------------+-------------------------------------------------------------+ 23 | | Name | Type | Description | 24 | +====================================+==============+=============================================================+ 25 | | [message=undefined] | String|Error | See :ref:`OAuthError#constructor`. | 26 | +------------------------------------+--------------+-------------------------------------------------------------+ 27 | | [properties={}] | Object | See :ref:`OAuthError#constructor`. | 28 | +------------------------------------+--------------+-------------------------------------------------------------+ 29 | | [properties.code=400] | Object | See :ref:`OAuthError#constructor`. | 30 | +------------------------------------+--------------+-------------------------------------------------------------+ 31 | | [properties.name='invalid_client'] | String | The error name used in responses generated from this error. | 32 | +------------------------------------+--------------+-------------------------------------------------------------+ 33 | 34 | **Return value:** 35 | 36 | A new instance of ``InvalidClientError``. 37 | 38 | **Remarks:** 39 | 40 | :: 41 | 42 | const err = new InvalidClientError(); 43 | // err.message === 'Bad Request' 44 | // err.code === 400 45 | // err.name === 'invalid_client' 46 | 47 | -------- 48 | 49 | .. _InvalidClientError#message: 50 | 51 | ``message`` 52 | =========== 53 | 54 | See :ref:`OAuthError#message `. 55 | 56 | -------- 57 | 58 | .. _InvalidClientError#code: 59 | 60 | ``code`` 61 | ======== 62 | 63 | Typically ``400``. See :ref:`OAuthError#code `. 64 | 65 | -------- 66 | 67 | .. _InvalidClientError#inner: 68 | 69 | ``inner`` 70 | ========= 71 | 72 | See :ref:`OAuthError#inner `. 73 | 74 | -------- 75 | 76 | .. _InvalidClientError#name: 77 | 78 | ``name`` 79 | ======== 80 | 81 | Typically ``'invalid_client'``. See :ref:`OAuthError#name `. 82 | 83 | -------------------------------------------------------------------------------- /docs/api/errors/invalid-grant-error.rst: -------------------------------------------------------------------------------- 1 | =================== 2 | InvalidGrantError 3 | =================== 4 | 5 | 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 :rfc:`Section 5.2 of RFC 6749 <6749#section-5.2>`. 6 | 7 | :: 8 | 9 | const InvalidGrantError = require('oauth2-server/lib/errors/invalid-grant-error'); 10 | 11 | -------- 12 | 13 | .. _InvalidGrantError#constructor: 14 | 15 | ``new InvalidGrantError(message, properties)`` 16 | ============================================== 17 | 18 | Instantiates an ``InvalidGrantError``. 19 | 20 | **Arguments:** 21 | 22 | +-----------------------------------+--------------+-------------------------------------------------------------+ 23 | | Name | Type | Description | 24 | +===================================+==============+=============================================================+ 25 | | [message=undefined] | String|Error | See :ref:`OAuthError#constructor`. | 26 | +-----------------------------------+--------------+-------------------------------------------------------------+ 27 | | [properties={}] | Object | See :ref:`OAuthError#constructor`. | 28 | +-----------------------------------+--------------+-------------------------------------------------------------+ 29 | | [properties.code=400] | Object | See :ref:`OAuthError#constructor`. | 30 | +-----------------------------------+--------------+-------------------------------------------------------------+ 31 | | [properties.name='invalid_grant'] | String | The error name used in responses generated from this error. | 32 | +-----------------------------------+--------------+-------------------------------------------------------------+ 33 | 34 | **Return value:** 35 | 36 | A new instance of ``InvalidGrantError``. 37 | 38 | **Remarks:** 39 | 40 | :: 41 | 42 | const err = new InvalidGrantError(); 43 | // err.message === 'Bad Request' 44 | // err.code === 400 45 | // err.name === 'invalid_grant' 46 | 47 | -------- 48 | 49 | .. _InvalidGrantError#message: 50 | 51 | ``message`` 52 | =========== 53 | 54 | See :ref:`OAuthError#message `. 55 | 56 | -------- 57 | 58 | .. _InvalidGrantError#code: 59 | 60 | ``code`` 61 | ======== 62 | 63 | Typically ``400``. See :ref:`OAuthError#code `. 64 | 65 | -------- 66 | 67 | .. _InvalidGrantError#inner: 68 | 69 | ``inner`` 70 | ========= 71 | 72 | See :ref:`OAuthError#inner `. 73 | 74 | -------- 75 | 76 | .. _InvalidGrantError#name: 77 | 78 | ``name`` 79 | ======== 80 | 81 | Typically ``'invalid_grant'``. See :ref:`OAuthError#name `. 82 | 83 | -------------------------------------------------------------------------------- /docs/api/errors/invalid-request-error.rst: -------------------------------------------------------------------------------- 1 | ===================== 2 | InvalidRequestError 3 | ===================== 4 | 5 | The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. See :rfc:`Section 4.2.2.1 of RFC 6749 <6749#section-4.2.2.1>`. 6 | 7 | :: 8 | 9 | const InvalidRequestError = require('oauth2-server/lib/errors/invalid-request-error'); 10 | 11 | -------- 12 | 13 | .. _InvalidRequestError#constructor: 14 | 15 | ``new InvalidRequestError(message, properties)`` 16 | ================================================ 17 | 18 | Instantiates an ``InvalidRequestError``. 19 | 20 | **Arguments:** 21 | 22 | +-------------------------------------+--------------+-------------------------------------------------------------+ 23 | | Name | Type | Description | 24 | +=====================================+==============+=============================================================+ 25 | | [message=undefined] | String|Error | See :ref:`OAuthError#constructor`. | 26 | +-------------------------------------+--------------+-------------------------------------------------------------+ 27 | | [properties={}] | Object | See :ref:`OAuthError#constructor`. | 28 | +-------------------------------------+--------------+-------------------------------------------------------------+ 29 | | [properties.code=400] | Object | See :ref:`OAuthError#constructor`. | 30 | +-------------------------------------+--------------+-------------------------------------------------------------+ 31 | | [properties.name='invalid_request'] | String | The error name used in responses generated from this error. | 32 | +-------------------------------------+--------------+-------------------------------------------------------------+ 33 | 34 | **Return value:** 35 | 36 | A new instance of ``InvalidRequestError``. 37 | 38 | **Remarks:** 39 | 40 | :: 41 | 42 | const err = new InvalidRequestError(); 43 | // err.message === 'Bad Request' 44 | // err.code === 400 45 | // err.name === 'invalid_request' 46 | 47 | -------- 48 | 49 | .. _InvalidRequestError#message: 50 | 51 | ``message`` 52 | =========== 53 | 54 | See :ref:`OAuthError#message `. 55 | 56 | -------- 57 | 58 | .. _InvalidRequestError#code: 59 | 60 | ``code`` 61 | ======== 62 | 63 | Typically ``400``. See :ref:`OAuthError#code `. 64 | 65 | -------- 66 | 67 | .. _InvalidRequestError#inner: 68 | 69 | ``inner`` 70 | ========= 71 | 72 | See :ref:`OAuthError#inner `. 73 | 74 | -------- 75 | 76 | .. _InvalidRequestError#name: 77 | 78 | ``name`` 79 | ======== 80 | 81 | Typically ``'invalid_request'``. See :ref:`OAuthError#name `. 82 | 83 | -------------------------------------------------------------------------------- /docs/api/errors/invalid-scope-error.rst: -------------------------------------------------------------------------------- 1 | =================== 2 | InvalidScopeError 3 | =================== 4 | 5 | The requested scope is invalid, unknown, or malformed. See :rfc:`Section 4.1.2.1 of RFC 6749 <6749#section-4.1.2.1>`. 6 | 7 | :: 8 | 9 | const InvalidScopeError = require('oauth2-server/lib/errors/invalid-scope-error'); 10 | 11 | -------- 12 | 13 | .. _InvalidScopeError#constructor: 14 | 15 | ``new InvalidScopeError(message, properties)`` 16 | ============================================== 17 | 18 | Instantiates an ``InvalidScopeError``. 19 | 20 | **Arguments:** 21 | 22 | +-----------------------------------+--------------+-------------------------------------------------------------+ 23 | | Name | Type | Description | 24 | +===================================+==============+=============================================================+ 25 | | [message=undefined] | String|Error | See :ref:`OAuthError#constructor`. | 26 | +-----------------------------------+--------------+-------------------------------------------------------------+ 27 | | [properties={}] | Object | See :ref:`OAuthError#constructor`. | 28 | +-----------------------------------+--------------+-------------------------------------------------------------+ 29 | | [properties.code=400] | Object | See :ref:`OAuthError#constructor`. | 30 | +-----------------------------------+--------------+-------------------------------------------------------------+ 31 | | [properties.name='invalid_scope'] | String | The error name used in responses generated from this error. | 32 | +-----------------------------------+--------------+-------------------------------------------------------------+ 33 | 34 | **Return value:** 35 | 36 | A new instance of ``InvalidScopeError``. 37 | 38 | **Remarks:** 39 | 40 | :: 41 | 42 | const err = new InvalidScopeError(); 43 | // err.message === 'Bad Request' 44 | // err.code === 400 45 | // err.name === 'invalid_scope' 46 | 47 | -------- 48 | 49 | .. _InvalidScopeError#message: 50 | 51 | ``message`` 52 | =========== 53 | 54 | See :ref:`OAuthError#message `. 55 | 56 | -------- 57 | 58 | .. _InvalidScopeError#code: 59 | 60 | ``code`` 61 | ======== 62 | 63 | Typically ``400``. See :ref:`OAuthError#code `. 64 | 65 | -------- 66 | 67 | .. _InvalidScopeError#inner: 68 | 69 | ``inner`` 70 | ========= 71 | 72 | See :ref:`OAuthError#inner `. 73 | 74 | -------- 75 | 76 | .. _InvalidScopeError#name: 77 | 78 | ``name`` 79 | ======== 80 | 81 | Typically ``'invalid_scope'``. See :ref:`OAuthError#name `. 82 | 83 | -------------------------------------------------------------------------------- /docs/api/errors/invalid-token-error.rst: -------------------------------------------------------------------------------- 1 | =================== 2 | InvalidTokenError 3 | =================== 4 | 5 | The access token provided is expired, revoked, malformed, or invalid for other reasons. See :rfc:`Section 3.1 of RFC 6750 <6750#section-3.1>`. 6 | 7 | :: 8 | 9 | const InvalidTokenError = require('oauth2-server/lib/errors/invalid-token-error'); 10 | 11 | -------- 12 | 13 | .. _InvalidTokenError#constructor: 14 | 15 | ``new InvalidTokenError(message, properties)`` 16 | ============================================== 17 | 18 | Instantiates an ``InvalidTokenError``. 19 | 20 | **Arguments:** 21 | 22 | +-----------------------------------+--------------+-------------------------------------------------------------+ 23 | | Name | Type | Description | 24 | +===================================+==============+=============================================================+ 25 | | [message=undefined] | String|Error | See :ref:`OAuthError#constructor`. | 26 | +-----------------------------------+--------------+-------------------------------------------------------------+ 27 | | [properties={}] | Object | See :ref:`OAuthError#constructor`. | 28 | +-----------------------------------+--------------+-------------------------------------------------------------+ 29 | | [properties.code=401] | Object | See :ref:`OAuthError#constructor`. | 30 | +-----------------------------------+--------------+-------------------------------------------------------------+ 31 | | [properties.name='invalid_token'] | String | The error name used in responses generated from this error. | 32 | +-----------------------------------+--------------+-------------------------------------------------------------+ 33 | 34 | **Return value:** 35 | 36 | A new instance of ``InvalidTokenError``. 37 | 38 | **Remarks:** 39 | 40 | :: 41 | 42 | const err = new InvalidTokenError(); 43 | // err.message === 'Unauthorized' 44 | // err.code === 401 45 | // err.name === 'invalid_token' 46 | 47 | -------- 48 | 49 | .. _InvalidTokenError#message: 50 | 51 | ``message`` 52 | =========== 53 | 54 | See :ref:`OAuthError#message `. 55 | 56 | -------- 57 | 58 | .. _InvalidTokenError#code: 59 | 60 | ``code`` 61 | ======== 62 | 63 | Typically ``401``. See :ref:`OAuthError#code `. 64 | 65 | -------- 66 | 67 | .. _InvalidTokenError#inner: 68 | 69 | ``inner`` 70 | ========= 71 | 72 | See :ref:`OAuthError#inner `. 73 | 74 | -------- 75 | 76 | .. _InvalidTokenError#name: 77 | 78 | ``name`` 79 | ======== 80 | 81 | Typically ``'invalid_token'``. See :ref:`OAuthError#name `. 82 | 83 | -------------------------------------------------------------------------------- /docs/api/errors/oauth-error.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | OAuthError 3 | ============ 4 | 5 | Base class for all errors returned by this module. 6 | 7 | :: 8 | 9 | const OAuthError = require('oauth2-server/lib/errors/oauth-error'); 10 | 11 | -------- 12 | 13 | .. _OAuthError#constructor: 14 | 15 | ``new OAuthError(message, properties)`` 16 | ======================================= 17 | 18 | Instantiates ``OAuthError``. 19 | 20 | .. note:: Do not use ``OAuthError`` directly; it's intended to be used as a base class. Instead, use one of the other error types derived from it. 21 | 22 | **Arguments:** 23 | 24 | +-----------------------------+--------------+------------------------------------------------------------------------------------+ 25 | | Name | Type | Description | 26 | +=============================+==============+====================================================================================+ 27 | | [message=undefined] | String|Error | Error message or nested exception. | 28 | +-----------------------------+--------------+------------------------------------------------------------------------------------+ 29 | | [properties={}] | Object | Additional properties to be set on the error object. | 30 | +-----------------------------+--------------+------------------------------------------------------------------------------------+ 31 | | [properties.code=500] | Object | An HTTP status code associated with the error. | 32 | +-----------------------------+--------------+------------------------------------------------------------------------------------+ 33 | | [properties.name=undefined] | String | The name of the error. If left undefined, the name is copied from the constructor. | 34 | +-----------------------------+--------------+------------------------------------------------------------------------------------+ 35 | 36 | **Return value:** 37 | 38 | A new instance of ``OAuthError``. 39 | 40 | **Remarks:** 41 | 42 | By default ``code`` is set to ``500`` and ``message`` is set to the respective HTTP phrase. 43 | 44 | :: 45 | 46 | const err = new OAuthError(); 47 | // err.message === 'Internal Server Error' 48 | // err.code === 500 49 | // err.name === 'OAuthError' 50 | 51 | :: 52 | 53 | const err = new OAuthError('test', {name: 'test_error'}); 54 | // err.message === 'test' 55 | // err.code === 500 56 | // err.name === 'test_error' 57 | 58 | :: 59 | 60 | const err = new OAuthError(undefined, {code: 404}); 61 | // err.message === 'Not Found' 62 | // err.code === 404 63 | // err.name === 'OAuthError' 64 | 65 | All additional ``properties`` are copied to the error object. 66 | 67 | :: 68 | 69 | const err = new OAuthError('test', {foo: 'bar', baz: 1234}); 70 | // err.message === 'test' 71 | // err.code === 500 72 | // err.name === 'OAuthError' 73 | // err.foo === 'bar' 74 | // err.baz === 1234 75 | 76 | When wrapping an exception, the ``message`` property is automatically copied from the existing exception. 77 | 78 | :: 79 | 80 | const anotherError = new Error('test'); 81 | const err = new OAuthError(e); 82 | // err.message === 'test' 83 | // err.code === 500 84 | // err.name === 'OAuthError' 85 | // err.inner === anotherError 86 | 87 | -------- 88 | 89 | .. _OAuthError#message: 90 | 91 | ``message`` 92 | =========== 93 | 94 | A message describing the error. 95 | 96 | -------- 97 | 98 | .. _OAuthError#code: 99 | 100 | ``code`` 101 | ======== 102 | 103 | An HTTP status code associated with the error. 104 | 105 | For compatibility reasons, two more properties exist that have the same value as ``code``: ``status`` and ``statusCode``. Note that changes to one of these are not reflected by the other properties. 106 | 107 | -------- 108 | 109 | .. _OAuthError#inner: 110 | 111 | ``inner`` 112 | ========= 113 | 114 | Another exception that was wrapped by this ``OAuthError`` instance. This property is set only if the error is constructed from an existing exception. 115 | 116 | -------- 117 | 118 | .. _OAuthError#name: 119 | 120 | ``name`` 121 | ======== 122 | 123 | The name of the error, intended to be used as the ``error`` parameter as described by :rfc:`6749` in :rfc:`Section 4.1.2.1 <6749#section-4.1.2.1>`, :rfc:`Section 4.2.2.1 <6749#section-4.2.2.1>` and :rfc:`Section 5.2 <6749#section-5.2>`, as well as :rfc:`Section 3.1 of RFC 6750 <6750#section-3.1>`. 124 | 125 | -------------------------------------------------------------------------------- /docs/api/errors/server-error.rst: -------------------------------------------------------------------------------- 1 | ============= 2 | ServerError 3 | ============= 4 | 5 | The authorization server encountered an unexpected condition that prevented it from fulfilling the request. See :rfc:`Section 4.1.2.1 of RFC 6749 <6749#section-4.1.2.1>`. 6 | 7 | :: 8 | 9 | const ServerError = require('oauth2-server/lib/errors/server-error'); 10 | 11 | ``ServerError`` is used to wrap unknown exceptions encountered during request processing. 12 | 13 | -------- 14 | 15 | .. _ServerError#constructor: 16 | 17 | ``new ServerError(message, properties)`` 18 | ======================================== 19 | 20 | Instantiates an ``ServerError``. 21 | 22 | **Arguments:** 23 | 24 | +----------------------------------+--------------+-------------------------------------------------------------+ 25 | | Name | Type | Description | 26 | +==================================+==============+=============================================================+ 27 | | [message=undefined] | String|Error | See :ref:`OAuthError#constructor`. | 28 | +----------------------------------+--------------+-------------------------------------------------------------+ 29 | | [properties={}] | Object | See :ref:`OAuthError#constructor`. | 30 | +----------------------------------+--------------+-------------------------------------------------------------+ 31 | | [properties.code=503] | Object | See :ref:`OAuthError#constructor`. | 32 | +----------------------------------+--------------+-------------------------------------------------------------+ 33 | | [properties.name='server_error'] | String | The error name used in responses generated from this error. | 34 | +----------------------------------+--------------+-------------------------------------------------------------+ 35 | 36 | **Return value:** 37 | 38 | A new instance of ``ServerError``. 39 | 40 | **Remarks:** 41 | 42 | :: 43 | 44 | const err = new ServerError(); 45 | // err.message === 'Service Unavailable Error' 46 | // err.code === 503 47 | // err.name === 'server_error' 48 | 49 | -------- 50 | 51 | .. _ServerError#message: 52 | 53 | ``message`` 54 | =========== 55 | 56 | See :ref:`OAuthError#message `. 57 | 58 | -------- 59 | 60 | .. _ServerError#code: 61 | 62 | ``code`` 63 | ======== 64 | 65 | Typically ``503``. See :ref:`OAuthError#code `. 66 | 67 | -------- 68 | 69 | .. _ServerError#inner: 70 | 71 | ``inner`` 72 | ========= 73 | 74 | See :ref:`OAuthError#inner `. 75 | 76 | -------- 77 | 78 | .. _ServerError#name: 79 | 80 | ``name`` 81 | ======== 82 | 83 | Typically ``'server_error'``. See :ref:`OAuthError#name `. 84 | 85 | -------------------------------------------------------------------------------- /docs/api/errors/unauthorized-client-error.rst: -------------------------------------------------------------------------------- 1 | ========================= 2 | UnauthorizedClientError 3 | ========================= 4 | 5 | The authenticated client is not authorized to use this authorization grant type. See :rfc:`Section 4.1.2.1 of RFC 6749 <6749#section-4.1.2.1>`. 6 | 7 | :: 8 | 9 | const UnauthorizedClientError = require('oauth2-server/lib/errors/unauthorized-client-error'); 10 | 11 | -------- 12 | 13 | .. _UnauthorizedClientError#constructor: 14 | 15 | ``new UnauthorizedClientError(message, properties)`` 16 | ==================================================== 17 | 18 | Instantiates an ``UnauthorizedClientError``. 19 | 20 | **Arguments:** 21 | 22 | +-----------------------------------------+--------------+-------------------------------------------------------------+ 23 | | Name | Type | Description | 24 | +=========================================+==============+=============================================================+ 25 | | [message=undefined] | String|Error | See :ref:`OAuthError#constructor`. | 26 | +-----------------------------------------+--------------+-------------------------------------------------------------+ 27 | | [properties={}] | Object | See :ref:`OAuthError#constructor`. | 28 | +-----------------------------------------+--------------+-------------------------------------------------------------+ 29 | | [properties.code=400] | Object | See :ref:`OAuthError#constructor`. | 30 | +-----------------------------------------+--------------+-------------------------------------------------------------+ 31 | | [properties.name='unauthorized_client'] | String | The error name used in responses generated from this error. | 32 | +-----------------------------------------+--------------+-------------------------------------------------------------+ 33 | 34 | **Return value:** 35 | 36 | A new instance of ``UnauthorizedClientError``. 37 | 38 | **Remarks:** 39 | 40 | :: 41 | 42 | const err = new UnauthorizedClientError(); 43 | // err.message === 'Bad Request' 44 | // err.code === 400 45 | // err.name === 'unauthorized_client' 46 | 47 | -------- 48 | 49 | .. _UnauthorizedClientError#message: 50 | 51 | ``message`` 52 | =========== 53 | 54 | See :ref:`OAuthError#message `. 55 | 56 | -------- 57 | 58 | .. _UnauthorizedClientError#code: 59 | 60 | ``code`` 61 | ======== 62 | 63 | Typically ``400``. See :ref:`OAuthError#code `. 64 | 65 | -------- 66 | 67 | .. _UnauthorizedClientError#inner: 68 | 69 | ``inner`` 70 | ========= 71 | 72 | See :ref:`OAuthError#inner `. 73 | 74 | -------- 75 | 76 | .. _UnauthorizedClientError#name: 77 | 78 | ``name`` 79 | ======== 80 | 81 | Typically ``'unauthorized_client'``. See :ref:`OAuthError#name `. 82 | 83 | -------------------------------------------------------------------------------- /docs/api/errors/unauthorized-request-error.rst: -------------------------------------------------------------------------------- 1 | ========================== 2 | UnauthorizedRequestError 3 | ========================== 4 | 5 | The request lacked any authentication information or the client attempted to use an unsupported authentication method. 6 | 7 | :: 8 | 9 | const UnauthorizedRequestError = require('oauth2-server/lib/errors/unauthorized-request-error'); 10 | 11 | According to :rfc:`Section 3.1 of RFC 6750 <6750#section-3.1>` you should just fail the request with ``401 Unauthorized`` and not send any error information in the body if this error occurs: 12 | 13 | If the request lacks any authentication information (e.g., the client 14 | was unaware that authentication is necessary or attempted using an 15 | unsupported authentication method), the resource server SHOULD NOT 16 | include an error code or other error information. 17 | 18 | -------- 19 | 20 | .. _UnauthorizedRequestError#constructor: 21 | 22 | ``new UnauthorizedRequestError(message, properties)`` 23 | ===================================================== 24 | 25 | Instantiates an ``UnauthorizedRequestError``. 26 | 27 | **Arguments:** 28 | 29 | +------------------------------------------+--------------+-------------------------------------------------------------+ 30 | | Name | Type | Description | 31 | +==========================================+==============+=============================================================+ 32 | | [message=undefined] | String|Error | See :ref:`OAuthError#constructor`. | 33 | +------------------------------------------+--------------+-------------------------------------------------------------+ 34 | | [properties={}] | Object | See :ref:`OAuthError#constructor`. | 35 | +------------------------------------------+--------------+-------------------------------------------------------------+ 36 | | [properties.code=401] | Object | See :ref:`OAuthError#constructor`. | 37 | +------------------------------------------+--------------+-------------------------------------------------------------+ 38 | | [properties.name='unauthorized_request'] | String | The error name used in responses generated from this error. | 39 | +------------------------------------------+--------------+-------------------------------------------------------------+ 40 | 41 | **Return value:** 42 | 43 | A new instance of ``UnauthorizedRequestError``. 44 | 45 | **Remarks:** 46 | 47 | :: 48 | 49 | const err = new UnauthorizedRequestError(); 50 | // err.message === 'Unauthorized' 51 | // err.code === 401 52 | // err.name === 'unauthorized_request' 53 | 54 | -------- 55 | 56 | .. _UnauthorizedRequestError#message: 57 | 58 | ``message`` 59 | =========== 60 | 61 | See :ref:`OAuthError#message `. 62 | 63 | -------- 64 | 65 | .. _UnauthorizedRequestError#code: 66 | 67 | ``code`` 68 | ======== 69 | 70 | Typically ``401``. See :ref:`OAuthError#code `. 71 | 72 | -------- 73 | 74 | .. _UnauthorizedRequestError#inner: 75 | 76 | ``inner`` 77 | ========= 78 | 79 | See :ref:`OAuthError#inner `. 80 | 81 | -------- 82 | 83 | .. _UnauthorizedRequestError#name: 84 | 85 | ``name`` 86 | ======== 87 | 88 | Typically ``'unauthorized_request'``. See :ref:`OAuthError#name `. 89 | 90 | -------------------------------------------------------------------------------- /docs/api/errors/unsupported-grant-type-error.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | UnsupportedGrantTypeError 3 | =========================== 4 | 5 | The authorization grant type is not supported by the authorization server. See :rfc:`Section 4.1.2.1 of RFC 6749 <6749#section-4.1.2.1>`. 6 | 7 | :: 8 | 9 | const UnsupportedGrantTypeError = require('oauth2-server/lib/errors/unsupported-grant-type-error'); 10 | 11 | -------- 12 | 13 | .. _UnsupportedGrantTypeError#constructor: 14 | 15 | ``new UnsupportedGrantTypeError(message, properties)`` 16 | ====================================================== 17 | 18 | Instantiates an ``UnsupportedGrantTypeError``. 19 | 20 | **Arguments:** 21 | 22 | +--------------------------------------------+--------------+-------------------------------------------------------------+ 23 | | Name | Type | Description | 24 | +============================================+==============+=============================================================+ 25 | | [message=undefined] | String|Error | See :ref:`OAuthError#constructor`. | 26 | +--------------------------------------------+--------------+-------------------------------------------------------------+ 27 | | [properties={}] | Object | See :ref:`OAuthError#constructor`. | 28 | +--------------------------------------------+--------------+-------------------------------------------------------------+ 29 | | [properties.code=400] | Object | See :ref:`OAuthError#constructor`. | 30 | +--------------------------------------------+--------------+-------------------------------------------------------------+ 31 | | [properties.name='unsupported_grant_type'] | String | The error name used in responses generated from this error. | 32 | +--------------------------------------------+--------------+-------------------------------------------------------------+ 33 | 34 | **Return value:** 35 | 36 | A new instance of ``UnsupportedGrantTypeError``. 37 | 38 | **Remarks:** 39 | 40 | :: 41 | 42 | const err = new UnsupportedGrantTypeError(); 43 | // err.message === 'Bad Request' 44 | // err.code === 400 45 | // err.name === 'unsupported_grant_type' 46 | 47 | -------- 48 | 49 | .. _UnsupportedGrantTypeError#message: 50 | 51 | ``message`` 52 | =========== 53 | 54 | See :ref:`OAuthError#message `. 55 | 56 | -------- 57 | 58 | .. _UnsupportedGrantTypeError#code: 59 | 60 | ``code`` 61 | ======== 62 | 63 | Typically ``400``. See :ref:`OAuthError#code `. 64 | 65 | -------- 66 | 67 | .. _UnsupportedGrantTypeError#inner: 68 | 69 | ``inner`` 70 | ========= 71 | 72 | See :ref:`OAuthError#inner `. 73 | 74 | -------- 75 | 76 | .. _UnsupportedGrantTypeError#name: 77 | 78 | ``name`` 79 | ======== 80 | 81 | Typically ``'unsupported_grant_type'``. See :ref:`OAuthError#name `. 82 | 83 | -------------------------------------------------------------------------------- /docs/api/errors/unsupported-response-type-error.rst: -------------------------------------------------------------------------------- 1 | ============================== 2 | UnsupportedResponseTypeError 3 | ============================== 4 | 5 | The authorization server does not supported obtaining an authorization code using this method. See :rfc:`Section 4.1.2.1 of RFC 6749 <6749#section-4.1.2.1>`. 6 | 7 | :: 8 | 9 | const UnsupportedResponseTypeError = require('oauth2-server/lib/errors/unsupported-response-type-error'); 10 | 11 | -------- 12 | 13 | .. _UnsupportedResponseTypeError#constructor: 14 | 15 | ``new UnsupportedResponseTypeError(message, properties)`` 16 | ========================================================= 17 | 18 | Instantiates an ``UnsupportedResponseTypeError``. 19 | 20 | **Arguments:** 21 | 22 | +-----------------------------------------------+--------------+-------------------------------------------------------------+ 23 | | Name | Type | Description | 24 | +===============================================+==============+=============================================================+ 25 | | [message=undefined] | String|Error | See :ref:`OAuthError#constructor`. | 26 | +-----------------------------------------------+--------------+-------------------------------------------------------------+ 27 | | [properties={}] | Object | See :ref:`OAuthError#constructor`. | 28 | +-----------------------------------------------+--------------+-------------------------------------------------------------+ 29 | | [properties.code=400] | Object | See :ref:`OAuthError#constructor`. | 30 | +-----------------------------------------------+--------------+-------------------------------------------------------------+ 31 | | [properties.name='unsupported_response_type'] | String | The error name used in responses generated from this error. | 32 | +-----------------------------------------------+--------------+-------------------------------------------------------------+ 33 | 34 | **Return value:** 35 | 36 | A new instance of ``UnsupportedResponseTypeError``. 37 | 38 | **Remarks:** 39 | 40 | :: 41 | 42 | const err = new UnsupportedResponseTypeError(); 43 | // err.message === 'Bad Request' 44 | // err.code === 400 45 | // err.name === 'unsupported_response_type' 46 | 47 | -------- 48 | 49 | .. _UnsupportedResponseTypeError#message: 50 | 51 | ``message`` 52 | =========== 53 | 54 | See :ref:`OAuthError#message `. 55 | 56 | -------- 57 | 58 | .. _UnsupportedResponseTypeError#code: 59 | 60 | ``code`` 61 | ======== 62 | 63 | Typically ``400``. See :ref:`OAuthError#code `. 64 | 65 | -------- 66 | 67 | .. _UnsupportedResponseTypeError#inner: 68 | 69 | ``inner`` 70 | ========= 71 | 72 | See :ref:`OAuthError#inner `. 73 | 74 | -------- 75 | 76 | .. _UnsupportedResponseTypeError#name: 77 | 78 | ``name`` 79 | ======== 80 | 81 | Typically ``'unsupported_response_type'``. See :ref:`OAuthError#name `. 82 | 83 | -------------------------------------------------------------------------------- /docs/api/request.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | Request 3 | ========= 4 | 5 | Represents an incoming HTTP request. 6 | 7 | :: 8 | 9 | const Request = require('oauth2-server').Request; 10 | 11 | -------- 12 | 13 | .. _Request#constructor: 14 | 15 | ``new Request(options)`` 16 | ======================== 17 | 18 | Instantiates ``Request`` using the supplied options. 19 | 20 | **Arguments:** 21 | 22 | +-------------------+--------+--------------------------------------------------------+ 23 | | Name | Type | Description | 24 | +===================+========+========================================================+ 25 | | options | Object | Request options. | 26 | +-------------------+--------+--------------------------------------------------------+ 27 | | options.method | String | The HTTP method of the request. | 28 | +-------------------+--------+--------------------------------------------------------+ 29 | | options.query | Object | The request's query string parameters. | 30 | +-------------------+--------+--------------------------------------------------------+ 31 | | options.headers | Object | The request's HTTP header fields. | 32 | +-------------------+--------+--------------------------------------------------------+ 33 | | [options.body={}] | Object | Key-value pairs of data submitted in the request body. | 34 | +-------------------+--------+--------------------------------------------------------+ 35 | 36 | All additional own properties are copied to the new ``Request`` object as well. 37 | 38 | **Return value:** 39 | 40 | A new ``Request`` instance. 41 | 42 | **Remarks:** 43 | 44 | The names of HTTP header fields passed in as ``options.headers`` are converted to lower case. 45 | 46 | To convert `Express' request`_ to a ``Request`` simply pass ``req`` as ``options``: 47 | 48 | .. _Express' request: https://expressjs.com/en/4x/api.html#req 49 | 50 | :: 51 | 52 | function(req, res, next) { 53 | var request = new Request(req); 54 | // ... 55 | } 56 | 57 | -------- 58 | 59 | .. _Request#get: 60 | 61 | ``get(field)`` 62 | ============== 63 | 64 | Returns the specified HTTP header field. The match is case-insensitive. 65 | 66 | **Arguments:** 67 | 68 | +-------+--------+------------------------+ 69 | | Name | Type | Description | 70 | +=======+========+========================+ 71 | | field | String | The header field name. | 72 | +-------+--------+------------------------+ 73 | 74 | **Return value:** 75 | 76 | The value of the header field or ``undefined`` if the field does not exist. 77 | 78 | -------- 79 | 80 | .. _Request#is: 81 | 82 | ``is(types)`` 83 | ============= 84 | 85 | Checks if the request's ``Content-Type`` HTTP header matches any of the given MIME types. 86 | 87 | **Arguments:** 88 | 89 | +-------+----------------------+-----------------------------------+ 90 | | Name | Type | Description | 91 | +=======+======================+===================================+ 92 | | types | Array|String | The MIME type(s) to test against. | 93 | +-------+----------------------+-----------------------------------+ 94 | 95 | **Return value:** 96 | 97 | Returns the matching MIME type or ``false`` if there was no match. 98 | 99 | -------- 100 | 101 | .. _Request#method: 102 | 103 | ``method`` 104 | ========== 105 | 106 | The HTTP method of the request (``'GET'``, ``'POST'``, ``'PUT'``, ...). 107 | 108 | -------- 109 | 110 | .. _Request#query: 111 | 112 | ``query`` 113 | ========= 114 | 115 | The request's query string parameters. 116 | 117 | -------- 118 | 119 | .. _Request#headers: 120 | 121 | ``headers`` 122 | =========== 123 | 124 | The request's HTTP header fields. Prefer :ref:`Request#get() ` over accessing this object directly. 125 | 126 | -------- 127 | 128 | .. _Request#body: 129 | 130 | ``body`` 131 | ======== 132 | 133 | Key-value pairs of data submitted in the request body. 134 | 135 | -------------------------------------------------------------------------------- /docs/api/response.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | Response 3 | ========== 4 | 5 | Represents an outgoing HTTP response. 6 | 7 | :: 8 | 9 | const Response = require('oauth2-server').Response; 10 | 11 | -------- 12 | 13 | .. _Response#constructor: 14 | 15 | ``new Response(options)`` 16 | ========================= 17 | 18 | Instantiates ``Response`` using the supplied options. 19 | 20 | **Arguments:** 21 | 22 | +-------------------+--------+---------------------------------------------------------------+ 23 | | Name | Type | Description | 24 | +===================+========+===============================================================+ 25 | | options | Object | Response options. | 26 | +-------------------+--------+---------------------------------------------------------------+ 27 | | options.headers | Object | The response's HTTP header fields. | 28 | +-------------------+--------+---------------------------------------------------------------+ 29 | | [options.body={}] | Object | Key-value pairs of data to be submitted in the response body. | 30 | +-------------------+--------+---------------------------------------------------------------+ 31 | 32 | All additional own properties are copied to the new ``Response`` object as well. 33 | 34 | **Return value:** 35 | 36 | A new ``Response`` instance. 37 | 38 | **Remarks:** 39 | 40 | The names of HTTP header fields passed in as ``options.headers`` are converted to lower case. 41 | 42 | To convert `Express' response`_ to a ``Response`` simply pass ``res`` as ``options``: 43 | 44 | .. _Express' response: https://expressjs.com/en/4x/api.html#res 45 | 46 | :: 47 | 48 | function(req, res, next) { 49 | var response = new Response(res); 50 | // ... 51 | } 52 | 53 | -------- 54 | 55 | .. _Response#get: 56 | 57 | ``get(field)`` 58 | ============== 59 | 60 | Returns the specified HTTP header field. The match is case-insensitive. 61 | 62 | **Arguments:** 63 | 64 | +-------+--------+------------------------+ 65 | | Name | Type | Description | 66 | +=======+========+========================+ 67 | | field | String | The header field name. | 68 | +-------+--------+------------------------+ 69 | 70 | **Return value:** 71 | 72 | The value of the header field or ``undefined`` if the field does not exist. 73 | 74 | -------- 75 | 76 | .. _Response#set: 77 | 78 | ``set(field, value)`` 79 | ===================== 80 | 81 | Sets the specified HTTP header field. The match is case-insensitive. 82 | 83 | **Arguments:** 84 | 85 | +-------+--------+-------------------------+ 86 | | Name | Type | Description | 87 | +=======+========+=========================+ 88 | | field | String | The header field name. | 89 | +-------+--------+-------------------------+ 90 | | value | String | The header field value. | 91 | +-------+--------+-------------------------+ 92 | 93 | **Return value:** 94 | 95 | None. 96 | 97 | -------- 98 | 99 | .. _Response#redirect: 100 | 101 | ``redirect(url)`` 102 | ================= 103 | 104 | Redirects to the specified URL using ``302 Found``. 105 | 106 | **Arguments:** 107 | 108 | +------+--------+-------------------------+ 109 | | Name | Type | Description | 110 | +======+========+=========================+ 111 | | url | String | The URL to redirect to. | 112 | +------+--------+-------------------------+ 113 | 114 | **Return value:** 115 | 116 | None. 117 | 118 | **Remarks:** 119 | 120 | This is essentially a convenience function that sets ``status`` to ``302`` and the ``Location`` header to the provided URL. 121 | 122 | -------- 123 | 124 | .. _Response#status: 125 | 126 | ``status`` 127 | ========== 128 | 129 | The HTTP status of the response (default = ``200``). 130 | 131 | -------- 132 | 133 | .. _Response#headers: 134 | 135 | ``headers`` 136 | =========== 137 | 138 | The response's HTTP header fields. Prefer :ref:`Response#get() `/:ref:`Response#set() ` over accessing this object directly. 139 | 140 | -------- 141 | 142 | .. _Response#body: 143 | 144 | ``body`` 145 | ======== 146 | 147 | Key-value pairs of data to be submitted in the response body. 148 | 149 | -------------------------------------------------------------------------------- /docs/docs/adapters.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | Adapters 3 | ========== 4 | 5 | The *oauth2-server* module is typically not used directly but through one of the available adapters, converting the interface to a suitable one for the HTTP server framework in use. 6 | 7 | .. framework-agnostic but there are several officially supported adapters available for popular HTTP server frameworks such as Express_ and Koa_. 8 | 9 | - express-oauth-server_ for Express_ 10 | - koa-oauth-server_ for Koa_ 11 | 12 | .. _express-oauth-server: https://npmjs.org/package/express-oauth-server 13 | .. _Express: https://npmjs.org/package/express 14 | .. _koa-oauth-server: https://npmjs.org/package/koa-oauth-server 15 | .. _Koa: https://npmjs.org/package/koa 16 | 17 | 18 | Writing Adapters 19 | ================ 20 | 21 | Adapters typically do the following: 22 | 23 | - Inherit from :doc:`OAuth2Server `. 24 | 25 | - Override :ref:`authenticate() `, :ref:`authorize() ` and :ref:`token() `. 26 | 27 | Each of these functions should: 28 | 29 | - Create :doc:`Request ` and :doc:`Response ` objects from their framework-specific counterparts. 30 | 31 | - Call the original function. 32 | 33 | - Copy all fields from the :doc:`Response ` back to the framework-specific request object and send it. 34 | 35 | Adapters should preserve functionality provided by *oauth2-server* but are free to add additional features that make sense for the respective HTTP server framework. 36 | 37 | -------------------------------------------------------------------------------- /docs/docs/getting-started.rst: -------------------------------------------------------------------------------- 1 | ================= 2 | Getting Started 3 | ================= 4 | 5 | .. _installation: 6 | 7 | Installation 8 | ============ 9 | 10 | oauth2-server_ is available via npm_. 11 | 12 | .. _oauth2-server: https://npmjs.org/package/oauth2-server 13 | .. _npm: https://npmjs.org 14 | 15 | .. code-block:: sh 16 | 17 | $ npm install oauth2-server 18 | 19 | .. note:: The *oauth2-server* module is framework-agnostic but there are several officially supported adapters available for popular HTTP server frameworks such as Express_ and Koa_. If you're using one of those frameworks it is strongly recommended to use the respective adapter module instead of rolling your own. 20 | 21 | .. _Express: https://npmjs.org/package/express-oauth-server 22 | .. _Koa: https://npmjs.org/package/koa-oauth-server 23 | 24 | 25 | .. _features: 26 | 27 | Features 28 | ======== 29 | 30 | - Supports :ref:`authorization code `, :ref:`client credentials `, :ref:`refresh token ` and :ref:`password ` grant, as well as :ref:`extension grants `, with scopes. 31 | - Can be used with *promises*, *Node-style callbacks*, *ES6 generators* and *async*/*await* (using Babel_). 32 | - Fully :rfc:`6749` and :rfc:`6750` compliant. 33 | - Implicitly supports any form of storage, e.g. *PostgreSQL*, *MySQL*, *MongoDB*, *Redis*, etc. 34 | - Complete `test suite`_. 35 | 36 | .. _Babel: https://babeljs.io 37 | .. _test suite: https://github.com/oauthjs/node-oauth2-server/tree/master/test 38 | 39 | 40 | .. _quick-start: 41 | 42 | Quick Start 43 | =========== 44 | 45 | :doc:`/api/oauth2-server` 46 | 47 | :: 48 | 49 | const OAuth2Server = require('oauth2-server'); 50 | 51 | const oauth = new OAuth2Server({ 52 | model: require('./model') 53 | }); 54 | 55 | :doc:`/api/request` and :doc:`/api/response` 56 | 57 | :: 58 | 59 | const Request = OAuth2Server.Request; 60 | const Response = OAuth2Server.Response; 61 | 62 | let request = new Request({/*...*/}); 63 | let response = new Response({/*...*/}); 64 | 65 | :ref:`OAuth2Server#authenticate() ` 66 | 67 | :: 68 | 69 | oauth.authenticate(request, response) 70 | .then((token) => { 71 | // The request was successfully authenticated. 72 | }) 73 | .catch((err) => { 74 | // The request failed authentication. 75 | }); 76 | 77 | :ref:`OAuth2Server#authorize() ` 78 | 79 | :: 80 | 81 | const AccessDeniedError = require('oauth2-server/lib/errors/access-denied-error'); 82 | 83 | oauth.authorize(request, response) 84 | .then((code) => { 85 | // The resource owner granted the access request. 86 | }) 87 | .catch((err) => { 88 | if (err instanceof AccessDeniedError) { 89 | // The resource owner denied the access request. 90 | } else { 91 | // Access was not granted due to some other error condition. 92 | } 93 | }); 94 | 95 | :ref:`OAuth2Server#token() ` 96 | 97 | :: 98 | 99 | oauth.token(request, response) 100 | .then((token) => { 101 | // The resource owner granted the access request. 102 | }) 103 | .catch((err) => { 104 | // The request was invalid or not authorized. 105 | }); 106 | 107 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | oauth2-server 3 | =============== 4 | 5 | oauth2-server_ is a complete, compliant and well tested module for implementing an OAuth2 server in Node.js_. The project is `hosted on GitHub`_ and the included test suite is automatically `run on Travis CI`_. 6 | 7 | .. _oauth2-server: https://npmjs.org/package/oauth2-server 8 | .. _Node.js: https://nodejs.org 9 | .. _hosted on GitHub: https://github.com/oauthjs/node-oauth2-server 10 | .. _run on Travis CI: https://travis-ci.org/oauthjs/node-oauth2-server 11 | 12 | :ref:`installation` 13 | 14 | 15 | Example Usage 16 | ============= 17 | 18 | :: 19 | 20 | const OAuth2Server = require('oauth2-server'); 21 | const Request = OAuth2Server.Request; 22 | const Response = OAuth2Server.Response; 23 | 24 | const oauth = new OAuth2Server({ 25 | model: require('./model') 26 | }); 27 | 28 | let request = new Request({ 29 | method: 'GET', 30 | query: {}, 31 | headers: {Authorization: 'Bearer foobar'} 32 | }); 33 | 34 | let response = new Response({ 35 | headers: {} 36 | }); 37 | 38 | oauth.authenticate(request, response) 39 | .then((token) => { 40 | // The request was successfully authenticated. 41 | }) 42 | .catch((err) => { 43 | // The request failed authentication. 44 | }); 45 | 46 | See the :doc:`/model/spec` of what is required from the model passed to :doc:`/api/oauth2-server`. 47 | 48 | 49 | .. toctree:: 50 | :hidden: 51 | 52 | Home 53 | 54 | .. toctree:: 55 | :maxdepth: 2 56 | :caption: User Documentation 57 | :hidden: 58 | 59 | docs/getting-started 60 | docs/adapters 61 | 62 | .. toctree:: 63 | :maxdepth: 2 64 | :caption: API 65 | :includehidden: 66 | :hidden: 67 | 68 | api/oauth2-server 69 | api/request 70 | api/response 71 | api/errors/index 72 | 73 | .. toctree:: 74 | :maxdepth: 3 75 | :caption: Model 76 | :hidden: 77 | 78 | model/overview 79 | model/spec 80 | 81 | .. toctree:: 82 | :maxdepth: 2 83 | :caption: Miscellaneous 84 | :hidden: 85 | 86 | misc/extension-grants 87 | misc/migrating-v2-to-v3 88 | 89 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. linkcheck to check all external links for integrity 37 | echo. doctest to run all doctests embedded in the documentation if enabled 38 | goto end 39 | ) 40 | 41 | if "%1" == "clean" ( 42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 43 | del /q /s %BUILDDIR%\* 44 | goto end 45 | ) 46 | 47 | if "%1" == "html" ( 48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 49 | if errorlevel 1 exit /b 1 50 | echo. 51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 52 | goto end 53 | ) 54 | 55 | if "%1" == "dirhtml" ( 56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 57 | if errorlevel 1 exit /b 1 58 | echo. 59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 60 | goto end 61 | ) 62 | 63 | if "%1" == "singlehtml" ( 64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 68 | goto end 69 | ) 70 | 71 | if "%1" == "pickle" ( 72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished; now you can process the pickle files. 76 | goto end 77 | ) 78 | 79 | if "%1" == "json" ( 80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished; now you can process the JSON files. 84 | goto end 85 | ) 86 | 87 | if "%1" == "htmlhelp" ( 88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can run HTML Help Workshop with the ^ 92 | .hhp project file in %BUILDDIR%/htmlhelp. 93 | goto end 94 | ) 95 | 96 | if "%1" == "qthelp" ( 97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 98 | if errorlevel 1 exit /b 1 99 | echo. 100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 101 | .qhcp project file in %BUILDDIR%/qthelp, like this: 102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\oauth2-server.qhcp 103 | echo.To view the help file: 104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\oauth2-server.ghc 105 | goto end 106 | ) 107 | 108 | if "%1" == "devhelp" ( 109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 110 | if errorlevel 1 exit /b 1 111 | echo. 112 | echo.Build finished. 113 | goto end 114 | ) 115 | 116 | if "%1" == "epub" ( 117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 118 | if errorlevel 1 exit /b 1 119 | echo. 120 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 121 | goto end 122 | ) 123 | 124 | if "%1" == "latex" ( 125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 129 | goto end 130 | ) 131 | 132 | if "%1" == "text" ( 133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The text files are in %BUILDDIR%/text. 137 | goto end 138 | ) 139 | 140 | if "%1" == "man" ( 141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 145 | goto end 146 | ) 147 | 148 | if "%1" == "texinfo" ( 149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 150 | if errorlevel 1 exit /b 1 151 | echo. 152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 153 | goto end 154 | ) 155 | 156 | if "%1" == "gettext" ( 157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 158 | if errorlevel 1 exit /b 1 159 | echo. 160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 161 | goto end 162 | ) 163 | 164 | if "%1" == "changes" ( 165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 166 | if errorlevel 1 exit /b 1 167 | echo. 168 | echo.The overview file is in %BUILDDIR%/changes. 169 | goto end 170 | ) 171 | 172 | if "%1" == "linkcheck" ( 173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 174 | if errorlevel 1 exit /b 1 175 | echo. 176 | echo.Link check complete; look for any errors in the above output ^ 177 | or in %BUILDDIR%/linkcheck/output.txt. 178 | goto end 179 | ) 180 | 181 | if "%1" == "doctest" ( 182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 183 | if errorlevel 1 exit /b 1 184 | echo. 185 | echo.Testing of doctests in the sources finished, look at the ^ 186 | results in %BUILDDIR%/doctest/output.txt. 187 | goto end 188 | ) 189 | 190 | :end 191 | -------------------------------------------------------------------------------- /docs/misc/extension-grants.rst: -------------------------------------------------------------------------------- 1 | ================== 2 | Extension Grants 3 | ================== 4 | 5 | .. todo:: Describe how to implement extension grants. 6 | 7 | Extension grants are registered through :ref:`OAuth2Server#token() ` (``options.extendedGrantTypes``). 8 | 9 | -------------------------------------------------------------------------------- /docs/misc/migrating-v2-to-v3.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | Migrating from 2.x to 3.x 3 | =========================== 4 | 5 | This module is now promise-based but allows for **ES6 generators**, **async/await** (using *[babel](https://babeljs.io)* or node v7.6+), **node-style** callbacks and **promises** in your model. 6 | 7 | ----------- 8 | Middlewares 9 | ----------- 10 | 11 | The naming of the exposed middlewares has changed to match the OAuth2 _RFC_ more closely. Please refer to the table below: 12 | 13 | +-------------------+------------------------------------------------+ 14 | | oauth2-server 2.x | oauth2-server 3.x | 15 | +===================+================================================+ 16 | | authorise | authenticate | 17 | +-------------------+------------------------------------------------+ 18 | | authCodeGrant | authorize | 19 | +-------------------+------------------------------------------------+ 20 | | grant | token | 21 | +-------------------+------------------------------------------------+ 22 | | errorHandler | **removed** (now handled by external wrappers) | 23 | +-------------------+------------------------------------------------+ 24 | | lockdown | **removed** (specific to *Express* middleware) | 25 | +-------------------+------------------------------------------------+ 26 | 27 | -------------- 28 | Server options 29 | -------------- 30 | 31 | The following server options can be set when instantiating the OAuth service: 32 | 33 | * `addAcceptedScopesHeader`: **default true** Add the `X-Accepted-OAuth-Scopes` header with a list of scopes that will be accepted 34 | * `addAuthorizedScopesHeader`: **default true** Add the `X-OAuth-Scopes` header with a list of scopes that the user is authorized for 35 | * `allowBearerTokensInQueryString`: **default false** Determine if the bearer token can be included in the query string (i.e. `?access_token=`) for validation calls 36 | * `allowEmptyState`: **default false** If true, `state` can be empty or not passed. If false, `state` is required. 37 | * `authorizationCodeLifetime`: **default 300** Default number of seconds that the authorization code is active for 38 | * `accessTokenLifetime`: **default 3600** Default number of seconds that an access token is valid for 39 | * `refreshTokenLifetime`: **default 1209600** Default number of seconds that a refresh token is valid for 40 | * `allowExtendedTokenAttributes`: **default false** Allows additional attributes (such as `id_token`) to be included in token responses. 41 | * `requireClientAuthentication`: **default true for all grant types** Allow ability to set client/secret authentication to `false` for a specific grant type. 42 | 43 | The following server options have changed behavior in v3.0.0: 44 | 45 | * `accessTokenLifetime` can no longer be set to `null` to indicate a non-expiring token. The recommend alternative is to set accessTokenLifetime to a high value. 46 | 47 | The following server options have been removed in v3.0.0: 48 | 49 | * `grants`: **removed** (now returned by the `getClient` method). 50 | * `debug`: **removed** (not the responsibility of this module). 51 | * `clientIdRegex`: **removed** (the `getClient` method can return `undefined` or throw an error). 52 | * `passthroughErrors`: **removed** (not the responsibility of this module). 53 | * `continueAfterResponse`: **removed** (not the responsibility of this module). 54 | 55 | ------------------- 56 | Model specification 57 | ------------------- 58 | 59 | * `generateAccessToken(client, user, scope)` is **optional** and should return a `String`. 60 | * `generateAuthorizationCode()` is **optional** and should return a `String`. 61 | * `generateRefreshToken(client, user, scope)` is **optional** and should return a `String`. 62 | * `getAccessToken(token)` should return an object with: 63 | 64 | * `accessToken` (`String`) 65 | * `accessTokenExpiresAt` (`Date`) 66 | * `client` (`Object`), containing at least an `id` property that matches the supplied client 67 | * `scope` (optional `String`) 68 | * `user` (`Object`) 69 | 70 | * `getAuthCode()` was renamed to `getAuthorizationCode(code)` and should return: 71 | 72 | * `client` (`Object`), containing at least an `id` property that matches the supplied client 73 | * `expiresAt` (`Date`) 74 | * `redirectUri` (optional `String`) 75 | * `user` (`Object`) 76 | 77 | * `getClient(clientId, clientSecret)` should return an object with, at minimum: 78 | 79 | * `redirectUris` (`Array`) 80 | * `grants` (`Array`) 81 | 82 | * `getRefreshToken(token)` should return an object with: 83 | 84 | * `refreshToken` (`String`) 85 | * `client` (`Object`), containing at least an `id` property that matches the supplied client 86 | * `refreshTokenExpiresAt` (optional `Date`) 87 | * `scope` (optional `String`) 88 | * `user` (`Object`) 89 | 90 | * `getUser(username, password)` should return an object: 91 | 92 | * No longer requires that `id` be returned. 93 | 94 | * `getUserFromClient(client)` should return an object: 95 | 96 | * No longer requires that `id` be returned. 97 | 98 | * `grantTypeAllowed()` was **removed**. You can instead: 99 | 100 | * Return *falsy* in your `getClient()` 101 | * Throw an error in your `getClient()` 102 | 103 | * `revokeAuthorizationCode(code)` is **required** and should return true 104 | * `revokeToken(token)` is **required** and should return true 105 | * `saveAccessToken()` was renamed to `saveToken(token, client, user)` and should return: 106 | 107 | * `accessToken` (`String`) 108 | * `accessTokenExpiresAt` (`Date`) 109 | * `client` (`Object`) 110 | * `refreshToken` (optional `String`) 111 | * `refreshTokenExpiresAt` (optional `Date`) 112 | * `user` (`Object`) 113 | 114 | * `saveAuthCode()` was renamed to `saveAuthorizationCode(code, client, user)` and should return: 115 | 116 | * `authorizationCode` (`String`) 117 | 118 | * `validateScope(user, client, scope)` should return a `Boolean`. 119 | 120 | The full model specification is [also available](https://oauth2-server.readthedocs.io/en/latest/model/spec.html). 121 | -------------------------------------------------------------------------------- /docs/model/overview.rst: -------------------------------------------------------------------------------- 1 | ================ 2 | Model Overview 3 | ================ 4 | 5 | :doc:`/api/oauth2-server` requires a model object through which some aspects of storage, retrieval and custom validation are abstracted. 6 | 7 | -------- 8 | 9 | .. _GrantTypes: 10 | 11 | Grant Types 12 | =========== 13 | 14 | :rfc:`6749` describes a number of grants for a client application to acquire an access token. 15 | 16 | The following grant types are supported by *oauth2-server*: 17 | 18 | -------- 19 | 20 | .. _AuthorizationCodeGrant: 21 | 22 | Authorization Code Grant 23 | ------------------------ 24 | 25 | See :rfc:`Section 4.1 of RFC 6749 <6749#section-4.1>`. 26 | 27 | An authorization code is a credential representing the resource owner's authorization (to access its protected resources) which is used by the client to obtain an access token. 28 | 29 | Model functions used by the authorization code grant: 30 | 31 | - :ref:`Model#generateAccessToken` 32 | - :ref:`Model#generateRefreshToken` 33 | - :ref:`Model#generateAuthorizationCode` 34 | - :ref:`Model#getAuthorizationCode` 35 | - :ref:`Model#getClient` 36 | - :ref:`Model#saveToken` 37 | - :ref:`Model#saveAuthorizationCode` 38 | - :ref:`Model#revokeAuthorizationCode` 39 | - :ref:`Model#validateScope` 40 | 41 | -------- 42 | 43 | .. _ClientCredentialsGrant: 44 | 45 | Client Credentials Grant 46 | ------------------------ 47 | 48 | See :rfc:`Section 4.4 of RFC 6749 <6749#section-4.4>`. 49 | 50 | The client can request an access token using only its client credentials (or other supported means of authentication) when requesting access to the protected resources under its control. 51 | 52 | .. note:: The client credentials grant type **must** only be used by confidential clients. 53 | 54 | Model functions used by the client credentials grant: 55 | 56 | - :ref:`Model#generateAccessToken` 57 | - :ref:`Model#getClient` 58 | - :ref:`Model#getUserFromClient` 59 | - :ref:`Model#saveToken` 60 | - :ref:`Model#validateScope` 61 | 62 | -------- 63 | 64 | .. _RefreshTokenGrant: 65 | 66 | Refresh Token Grant 67 | ------------------- 68 | 69 | See :rfc:`Section 6 of RFC 6749 <6749#section-6>`. 70 | 71 | If the authorization server issued a refresh token to the client, the client can request a refresh of their authorization token. 72 | 73 | Model functions used by the refresh token grant: 74 | 75 | - :ref:`Model#generateRefreshToken` 76 | - :ref:`Model#getRefreshToken` 77 | - :ref:`Model#getClient` 78 | - :ref:`Model#saveToken` 79 | - :ref:`Model#revokeToken` 80 | 81 | -------- 82 | 83 | .. _PasswordGrant: 84 | 85 | Password Grant 86 | -------------- 87 | 88 | See :rfc:`Section 4.3 of RFC 6749 <6749#section-4.3>`. 89 | 90 | The password grant is suitable for clients capable of obtaining the resource owner's credentials (username and password, typically using an interactive form). 91 | 92 | Model functions used by the password grant: 93 | 94 | - :ref:`Model#generateAccessToken` 95 | - :ref:`Model#generateRefreshToken` 96 | - :ref:`Model#getClient` 97 | - :ref:`Model#getUser` 98 | - :ref:`Model#saveToken` 99 | - :ref:`Model#validateScope` 100 | 101 | -------- 102 | 103 | .. _ExtensionGrants: 104 | 105 | Extension Grants 106 | ---------------- 107 | 108 | See :rfc:`Section 4.5 of RFC 6749 <6749#section-4.5>`. 109 | 110 | The authorization server may also implement custom grant types to issue access (and optionally refresh) tokens. 111 | 112 | See :doc:`/misc/extension-grants`. 113 | 114 | -------- 115 | 116 | .. _RequestAuthentication: 117 | 118 | Request Authentication 119 | ====================== 120 | 121 | See :rfc:`Section 2 of RFC 6750 <6750#section-2>`. 122 | 123 | The authorization server authenticates requests sent to the resource server by verifying the included bearer token. 124 | 125 | Model functions used during request authentication: 126 | 127 | - :ref:`Model#getAccessToken` 128 | - :ref:`Model#verifyScope` 129 | 130 | -------------------------------------------------------------------------------- /docs/npm_conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ Helper module for sphinx' conf.py. 3 | 4 | Useful when building documentation for npm packages. 5 | """ 6 | 7 | import json 8 | import re 9 | from datetime import datetime 10 | 11 | def load_package_json(path): 12 | """ Loads package.json from 'path'. 13 | """ 14 | with open(path) as f: 15 | return json.load(f) 16 | 17 | def get_short_version(version): 18 | """ Extracts the short version ("x.y.z") from 'version'. 19 | """ 20 | match = re.match('^(\d+(?:\.\d+){2})', version) 21 | if not match: 22 | raise Error('invalid version') 23 | return match.group() 24 | 25 | def get_copyright_year(base_year): 26 | """ Returns the year(s) to be shown in the copyright notice. 27 | 28 | If base_year is the current year: 29 | 'nnnn' where nnnn is 'base_year' 30 | If the current year is larger than base_year: 31 | 'nnnn-mmmm' where nnnn is 'base_year' and mmmm is the current year 32 | """ 33 | this_year = datetime.now().year 34 | fmt = '{base_year}-{this_year}' if this_year > base_year else '{this_year}' 35 | return fmt.format(base_year=base_year, this_year=this_year) 36 | 37 | def get_config(): 38 | package = load_package_json('../package.json') 39 | return { 40 | 'name': package['name'], 41 | 'version': package['version'], 42 | 'short_version': get_short_version(package['version']), 43 | 'organization': 'oauthjs', 44 | 'copyright_year': get_copyright_year(2016), 45 | # TODO: Get authors from package. 46 | 'docs_author': 'Max Truxa', 47 | 'docs_author_email': 'dev@maxtruxa.com' 48 | } 49 | 50 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Expose server and request/response classes. 5 | */ 6 | 7 | exports = module.exports = require('./lib/server'); 8 | exports.Request = require('./lib/request'); 9 | exports.Response = require('./lib/response'); 10 | 11 | /** 12 | * Export helpers for extension grants. 13 | */ 14 | 15 | exports.AbstractGrantType = require('./lib/grant-types/abstract-grant-type'); 16 | 17 | /** 18 | * Export error classes. 19 | */ 20 | 21 | exports.AccessDeniedError = require('./lib/errors/access-denied-error'); 22 | exports.InsufficientScopeError = require('./lib/errors/insufficient-scope-error'); 23 | exports.InvalidArgumentError = require('./lib/errors/invalid-argument-error'); 24 | exports.InvalidClientError = require('./lib/errors/invalid-client-error'); 25 | exports.InvalidGrantError = require('./lib/errors/invalid-grant-error'); 26 | exports.InvalidRequestError = require('./lib/errors/invalid-request-error'); 27 | exports.InvalidScopeError = require('./lib/errors/invalid-scope-error'); 28 | exports.InvalidTokenError = require('./lib/errors/invalid-token-error'); 29 | exports.OAuthError = require('./lib/errors/oauth-error'); 30 | exports.ServerError = require('./lib/errors/server-error'); 31 | exports.UnauthorizedClientError = require('./lib/errors/unauthorized-client-error'); 32 | exports.UnauthorizedRequestError = require('./lib/errors/unauthorized-request-error'); 33 | exports.UnsupportedGrantTypeError = require('./lib/errors/unsupported-grant-type-error'); 34 | exports.UnsupportedResponseTypeError = require('./lib/errors/unsupported-response-type-error'); 35 | 36 | -------------------------------------------------------------------------------- /lib/errors/access-denied-error.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var _ = require('lodash'); 8 | var OAuthError = require('./oauth-error'); 9 | var util = require('util'); 10 | 11 | /** 12 | * Constructor. 13 | * 14 | * "The resource owner or authorization server denied the request" 15 | * 16 | * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 17 | */ 18 | 19 | function AccessDeniedError(message, properties) { 20 | properties = _.assign({ 21 | code: 400, 22 | name: 'access_denied' 23 | }, properties); 24 | 25 | OAuthError.call(this, message, properties); 26 | } 27 | 28 | /** 29 | * Inherit prototype. 30 | */ 31 | 32 | util.inherits(AccessDeniedError, OAuthError); 33 | 34 | /** 35 | * Export constructor. 36 | */ 37 | 38 | module.exports = AccessDeniedError; 39 | -------------------------------------------------------------------------------- /lib/errors/insufficient-scope-error.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var _ = require('lodash'); 8 | var OAuthError = require('./oauth-error'); 9 | var util = require('util'); 10 | 11 | /** 12 | * Constructor. 13 | * 14 | * "The request requires higher privileges than provided by the access token.." 15 | * 16 | * @see https://tools.ietf.org/html/rfc6750.html#section-3.1 17 | */ 18 | 19 | function InsufficientScopeError(message, properties) { 20 | properties = _.assign({ 21 | code: 403, 22 | name: 'insufficient_scope' 23 | }, properties); 24 | 25 | OAuthError.call(this, message, properties); 26 | } 27 | 28 | /** 29 | * Inherit prototype. 30 | */ 31 | 32 | util.inherits(InsufficientScopeError, OAuthError); 33 | 34 | /** 35 | * Export constructor. 36 | */ 37 | 38 | module.exports = InsufficientScopeError; 39 | -------------------------------------------------------------------------------- /lib/errors/invalid-argument-error.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var _ = require('lodash'); 8 | var OAuthError = require('./oauth-error'); 9 | var util = require('util'); 10 | 11 | /** 12 | * Constructor. 13 | */ 14 | 15 | function InvalidArgumentError(message, properties) { 16 | properties = _.assign({ 17 | code: 500, 18 | name: 'invalid_argument' 19 | }, properties); 20 | 21 | OAuthError.call(this, message, properties); 22 | } 23 | 24 | /** 25 | * Inherit prototype. 26 | */ 27 | 28 | util.inherits(InvalidArgumentError, OAuthError); 29 | 30 | /** 31 | * Export constructor. 32 | */ 33 | 34 | module.exports = InvalidArgumentError; 35 | -------------------------------------------------------------------------------- /lib/errors/invalid-client-error.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var _ = require('lodash'); 8 | var OAuthError = require('./oauth-error'); 9 | var util = require('util'); 10 | 11 | /** 12 | * Constructor. 13 | * 14 | * "Client authentication failed (e.g., unknown client, no client 15 | * authentication included, or unsupported authentication method)" 16 | * 17 | * @see https://tools.ietf.org/html/rfc6749#section-5.2 18 | */ 19 | 20 | function InvalidClientError(message, properties) { 21 | properties = _.assign({ 22 | code: 400, 23 | name: 'invalid_client' 24 | }, properties); 25 | 26 | OAuthError.call(this, message, properties); 27 | } 28 | 29 | /** 30 | * Inherit prototype. 31 | */ 32 | 33 | util.inherits(InvalidClientError, OAuthError); 34 | 35 | /** 36 | * Export constructor. 37 | */ 38 | 39 | module.exports = InvalidClientError; 40 | -------------------------------------------------------------------------------- /lib/errors/invalid-grant-error.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var _ = require('lodash'); 8 | var OAuthError = require('./oauth-error'); 9 | var util = require('util'); 10 | 11 | /** 12 | * Constructor. 13 | * 14 | * "The provided authorization grant (e.g., authorization code, resource owner credentials) 15 | * or refresh token is invalid, expired, revoked, does not match the redirection URI used 16 | * in the authorization request, or was issued to another client." 17 | * 18 | * @see https://tools.ietf.org/html/rfc6749#section-5.2 19 | */ 20 | 21 | function InvalidGrantError(message, properties) { 22 | properties = _.assign({ 23 | code: 400, 24 | name: 'invalid_grant' 25 | }, properties); 26 | 27 | OAuthError.call(this, message, properties); 28 | } 29 | 30 | /** 31 | * Inherit prototype. 32 | */ 33 | 34 | util.inherits(InvalidGrantError, OAuthError); 35 | 36 | /** 37 | * Export constructor. 38 | */ 39 | 40 | module.exports = InvalidGrantError; 41 | -------------------------------------------------------------------------------- /lib/errors/invalid-request-error.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var _ = require('lodash'); 8 | var OAuthError = require('./oauth-error'); 9 | var util = require('util'); 10 | 11 | /** 12 | * Constructor. 13 | * 14 | * "The request is missing a required parameter, includes an invalid parameter value, 15 | * includes a parameter more than once, or is otherwise malformed." 16 | * 17 | * @see https://tools.ietf.org/html/rfc6749#section-4.2.2.1 18 | */ 19 | 20 | function InvalidRequest(message, properties) { 21 | properties = _.assign({ 22 | code: 400, 23 | name: 'invalid_request' 24 | }, properties); 25 | 26 | OAuthError.call(this, message, properties); 27 | } 28 | 29 | /** 30 | * Inherit prototype. 31 | */ 32 | 33 | util.inherits(InvalidRequest, OAuthError); 34 | 35 | /** 36 | * Export constructor. 37 | */ 38 | 39 | module.exports = InvalidRequest; 40 | -------------------------------------------------------------------------------- /lib/errors/invalid-scope-error.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var _ = require('lodash'); 8 | var OAuthError = require('./oauth-error'); 9 | var util = require('util'); 10 | 11 | /** 12 | * Constructor. 13 | * 14 | * "The requested scope is invalid, unknown, or malformed." 15 | * 16 | * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 17 | */ 18 | 19 | function InvalidScopeError(message, properties) { 20 | properties = _.assign({ 21 | code: 400, 22 | name: 'invalid_scope' 23 | }, properties); 24 | 25 | OAuthError.call(this, message, properties); 26 | } 27 | 28 | /** 29 | * Inherit prototype. 30 | */ 31 | 32 | util.inherits(InvalidScopeError, OAuthError); 33 | 34 | /** 35 | * Export constructor. 36 | */ 37 | 38 | module.exports = InvalidScopeError; 39 | -------------------------------------------------------------------------------- /lib/errors/invalid-token-error.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var _ = require('lodash'); 8 | var OAuthError = require('./oauth-error'); 9 | var util = require('util'); 10 | 11 | /** 12 | * Constructor. 13 | * 14 | * "The access token provided is expired, revoked, malformed, or invalid for other reasons." 15 | * 16 | * @see https://tools.ietf.org/html/rfc6750#section-3.1 17 | */ 18 | 19 | function InvalidTokenError(message, properties) { 20 | properties = _.assign({ 21 | code: 401, 22 | name: 'invalid_token' 23 | }, properties); 24 | 25 | OAuthError.call(this, message, properties); 26 | } 27 | 28 | /** 29 | * Inherit prototype. 30 | */ 31 | 32 | util.inherits(InvalidTokenError, OAuthError); 33 | 34 | /** 35 | * Export constructor. 36 | */ 37 | 38 | module.exports = InvalidTokenError; 39 | -------------------------------------------------------------------------------- /lib/errors/oauth-error.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var _ = require('lodash'); 7 | var util = require('util'); 8 | var statuses = require('statuses'); 9 | /** 10 | * Constructor. 11 | */ 12 | 13 | function OAuthError(messageOrError, properties) { 14 | var message = messageOrError instanceof Error ? messageOrError.message : messageOrError; 15 | var error = messageOrError instanceof Error ? messageOrError : null; 16 | if (_.isEmpty(properties)) 17 | { 18 | properties = {}; 19 | } 20 | 21 | _.defaults(properties, { code: 500 }); 22 | 23 | if (error) { 24 | properties.inner = error; 25 | } 26 | if (_.isEmpty(message)) { 27 | message = statuses[properties.code]; 28 | } 29 | this.code = this.status = this.statusCode = properties.code; 30 | this.message = message; 31 | for (var key in properties) { 32 | if (key !== 'code') { 33 | this[key] = properties[key]; 34 | } 35 | } 36 | Error.captureStackTrace(this, OAuthError); 37 | } 38 | 39 | util.inherits(OAuthError, Error); 40 | 41 | /** 42 | * Export constructor. 43 | */ 44 | 45 | module.exports = OAuthError; 46 | -------------------------------------------------------------------------------- /lib/errors/server-error.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var _ = require('lodash'); 8 | var OAuthError = require('./oauth-error'); 9 | var util = require('util'); 10 | 11 | /** 12 | * Constructor. 13 | * 14 | * "The authorization server encountered an unexpected condition that prevented it from fulfilling the request." 15 | * 16 | * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 17 | */ 18 | 19 | function ServerError(message, properties) { 20 | properties = _.assign({ 21 | code: 503, 22 | name: 'server_error' 23 | }, properties); 24 | 25 | OAuthError.call(this, message, properties); 26 | } 27 | 28 | /** 29 | * Inherit prototype. 30 | */ 31 | 32 | util.inherits(ServerError, OAuthError); 33 | 34 | /** 35 | * Export constructor. 36 | */ 37 | 38 | module.exports = ServerError; 39 | -------------------------------------------------------------------------------- /lib/errors/unauthorized-client-error.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var _ = require('lodash'); 8 | var OAuthError = require('./oauth-error'); 9 | var util = require('util'); 10 | 11 | /** 12 | * Constructor. 13 | * 14 | * "The authenticated client is not authorized to use this authorization grant type." 15 | * 16 | * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 17 | */ 18 | 19 | function UnauthorizedClientError(message, properties) { 20 | properties = _.assign({ 21 | code: 400, 22 | name: 'unauthorized_client' 23 | }, properties); 24 | 25 | OAuthError.call(this, message, properties); 26 | } 27 | 28 | /** 29 | * Inherit prototype. 30 | */ 31 | 32 | util.inherits(UnauthorizedClientError, OAuthError); 33 | 34 | /** 35 | * Export constructor. 36 | */ 37 | 38 | module.exports = UnauthorizedClientError; 39 | -------------------------------------------------------------------------------- /lib/errors/unauthorized-request-error.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var _ = require('lodash'); 8 | var OAuthError = require('./oauth-error'); 9 | var util = require('util'); 10 | 11 | /** 12 | * Constructor. 13 | * 14 | * "If the request lacks any authentication information (e.g., the client 15 | * was unaware that authentication is necessary or attempted using an 16 | * unsupported authentication method), the resource server SHOULD NOT 17 | * include an error code or other error information." 18 | * 19 | * @see https://tools.ietf.org/html/rfc6750#section-3.1 20 | */ 21 | 22 | function UnauthorizedRequestError(message, properties) { 23 | properties = _.assign({ 24 | code: 401, 25 | name: 'unauthorized_request' 26 | }, properties); 27 | 28 | OAuthError.call(this, message, properties); 29 | } 30 | 31 | /** 32 | * Inherit prototype. 33 | */ 34 | 35 | util.inherits(UnauthorizedRequestError, OAuthError); 36 | 37 | /** 38 | * Export constructor. 39 | */ 40 | 41 | module.exports = UnauthorizedRequestError; 42 | -------------------------------------------------------------------------------- /lib/errors/unsupported-grant-type-error.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var _ = require('lodash'); 8 | var OAuthError = require('./oauth-error'); 9 | var util = require('util'); 10 | 11 | /** 12 | * Constructor. 13 | * 14 | * "The authorization grant type is not supported by the authorization server." 15 | * 16 | * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 17 | */ 18 | 19 | function UnsupportedGrantTypeError(message, properties) { 20 | properties = _.assign({ 21 | code: 400, 22 | name: 'unsupported_grant_type' 23 | }, properties); 24 | 25 | OAuthError.call(this, message, properties); 26 | } 27 | 28 | /** 29 | * Inherit prototype. 30 | */ 31 | 32 | util.inherits(UnsupportedGrantTypeError, OAuthError); 33 | 34 | /** 35 | * Export constructor. 36 | */ 37 | 38 | module.exports = UnsupportedGrantTypeError; 39 | -------------------------------------------------------------------------------- /lib/errors/unsupported-response-type-error.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var _ = require('lodash'); 8 | var OAuthError = require('./oauth-error'); 9 | var util = require('util'); 10 | 11 | /** 12 | * Constructor. 13 | * 14 | * "The authorization server does not supported obtaining an 15 | * authorization code using this method." 16 | * 17 | * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 18 | */ 19 | 20 | function UnsupportedResponseTypeError(message, properties) { 21 | properties = _.assign({ 22 | code: 400, 23 | name: 'unsupported_response_type' 24 | }, properties); 25 | 26 | OAuthError.call(this, message, properties); 27 | } 28 | 29 | /** 30 | * Inherit prototype. 31 | */ 32 | 33 | util.inherits(UnsupportedResponseTypeError, OAuthError); 34 | 35 | /** 36 | * Export constructor. 37 | */ 38 | 39 | module.exports = UnsupportedResponseTypeError; 40 | -------------------------------------------------------------------------------- /lib/grant-types/abstract-grant-type.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var InvalidArgumentError = require('../errors/invalid-argument-error'); 8 | var InvalidScopeError = require('../errors/invalid-scope-error'); 9 | var Promise = require('bluebird'); 10 | var promisify = require('promisify-any').use(Promise); 11 | var is = require('../validator/is'); 12 | var tokenUtil = require('../utils/token-util'); 13 | 14 | /** 15 | * Constructor. 16 | */ 17 | 18 | function AbstractGrantType(options) { 19 | options = options || {}; 20 | 21 | if (!options.accessTokenLifetime) { 22 | throw new InvalidArgumentError('Missing parameter: `accessTokenLifetime`'); 23 | } 24 | 25 | if (!options.model) { 26 | throw new InvalidArgumentError('Missing parameter: `model`'); 27 | } 28 | 29 | this.accessTokenLifetime = options.accessTokenLifetime; 30 | this.model = options.model; 31 | this.refreshTokenLifetime = options.refreshTokenLifetime; 32 | this.alwaysIssueNewRefreshToken = options.alwaysIssueNewRefreshToken; 33 | } 34 | 35 | /** 36 | * Generate access token. 37 | */ 38 | 39 | AbstractGrantType.prototype.generateAccessToken = function(client, user, scope) { 40 | if (this.model.generateAccessToken) { 41 | return promisify(this.model.generateAccessToken, 3).call(this.model, client, user, scope) 42 | .then(function(accessToken) { 43 | return accessToken || tokenUtil.generateRandomToken(); 44 | }); 45 | } 46 | 47 | return tokenUtil.generateRandomToken(); 48 | }; 49 | 50 | /** 51 | * Generate refresh token. 52 | */ 53 | 54 | AbstractGrantType.prototype.generateRefreshToken = function(client, user, scope) { 55 | if (this.model.generateRefreshToken) { 56 | return promisify(this.model.generateRefreshToken, 3).call(this.model, client, user, scope) 57 | .then(function(refreshToken) { 58 | return refreshToken || tokenUtil.generateRandomToken(); 59 | }); 60 | } 61 | 62 | return tokenUtil.generateRandomToken(); 63 | }; 64 | 65 | /** 66 | * Get access token expiration date. 67 | */ 68 | 69 | AbstractGrantType.prototype.getAccessTokenExpiresAt = function() { 70 | return new Date(Date.now() + this.accessTokenLifetime * 1000); 71 | }; 72 | 73 | /** 74 | * Get refresh token expiration date. 75 | */ 76 | 77 | AbstractGrantType.prototype.getRefreshTokenExpiresAt = function() { 78 | return new Date(Date.now() + this.refreshTokenLifetime * 1000); 79 | }; 80 | 81 | /** 82 | * Get scope from the request body. 83 | */ 84 | 85 | AbstractGrantType.prototype.getScope = function(request) { 86 | if (!is.nqschar(request.body.scope)) { 87 | throw new InvalidArgumentError('Invalid parameter: `scope`'); 88 | } 89 | 90 | return request.body.scope; 91 | }; 92 | 93 | /** 94 | * Validate requested scope. 95 | */ 96 | AbstractGrantType.prototype.validateScope = function(user, client, scope) { 97 | if (this.model.validateScope) { 98 | return promisify(this.model.validateScope, 3).call(this.model, user, client, scope) 99 | .then(function (scope) { 100 | if (!scope) { 101 | throw new InvalidScopeError('Invalid scope: Requested scope is invalid'); 102 | } 103 | 104 | return scope; 105 | }); 106 | } else { 107 | return scope; 108 | } 109 | }; 110 | 111 | /** 112 | * Export constructor. 113 | */ 114 | 115 | module.exports = AbstractGrantType; 116 | -------------------------------------------------------------------------------- /lib/grant-types/authorization-code-grant-type.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var AbstractGrantType = require('./abstract-grant-type'); 8 | var InvalidArgumentError = require('../errors/invalid-argument-error'); 9 | var InvalidGrantError = require('../errors/invalid-grant-error'); 10 | var InvalidRequestError = require('../errors/invalid-request-error'); 11 | var Promise = require('bluebird'); 12 | var promisify = require('promisify-any').use(Promise); 13 | var ServerError = require('../errors/server-error'); 14 | var is = require('../validator/is'); 15 | var util = require('util'); 16 | 17 | /** 18 | * Constructor. 19 | */ 20 | 21 | function AuthorizationCodeGrantType(options) { 22 | options = options || {}; 23 | 24 | if (!options.model) { 25 | throw new InvalidArgumentError('Missing parameter: `model`'); 26 | } 27 | 28 | if (!options.model.getAuthorizationCode) { 29 | throw new InvalidArgumentError('Invalid argument: model does not implement `getAuthorizationCode()`'); 30 | } 31 | 32 | if (!options.model.revokeAuthorizationCode) { 33 | throw new InvalidArgumentError('Invalid argument: model does not implement `revokeAuthorizationCode()`'); 34 | } 35 | 36 | if (!options.model.saveToken) { 37 | throw new InvalidArgumentError('Invalid argument: model does not implement `saveToken()`'); 38 | } 39 | 40 | AbstractGrantType.call(this, options); 41 | } 42 | 43 | /** 44 | * Inherit prototype. 45 | */ 46 | 47 | util.inherits(AuthorizationCodeGrantType, AbstractGrantType); 48 | 49 | /** 50 | * Handle authorization code grant. 51 | * 52 | * @see https://tools.ietf.org/html/rfc6749#section-4.1.3 53 | */ 54 | 55 | AuthorizationCodeGrantType.prototype.handle = function(request, client) { 56 | if (!request) { 57 | throw new InvalidArgumentError('Missing parameter: `request`'); 58 | } 59 | 60 | if (!client) { 61 | throw new InvalidArgumentError('Missing parameter: `client`'); 62 | } 63 | 64 | return Promise.bind(this) 65 | .then(function() { 66 | return this.getAuthorizationCode(request, client); 67 | }) 68 | .tap(function(code) { 69 | return this.validateRedirectUri(request, code); 70 | }) 71 | .tap(function(code) { 72 | return this.revokeAuthorizationCode(code); 73 | }) 74 | .then(function(code) { 75 | return this.saveToken(code.user, client, code.authorizationCode, code.scope); 76 | }); 77 | }; 78 | 79 | /** 80 | * Get the authorization code. 81 | */ 82 | 83 | AuthorizationCodeGrantType.prototype.getAuthorizationCode = function(request, client) { 84 | if (!request.body.code) { 85 | throw new InvalidRequestError('Missing parameter: `code`'); 86 | } 87 | 88 | if (!is.vschar(request.body.code)) { 89 | throw new InvalidRequestError('Invalid parameter: `code`'); 90 | } 91 | return promisify(this.model.getAuthorizationCode, 1).call(this.model, request.body.code) 92 | .then(function(code) { 93 | if (!code) { 94 | throw new InvalidGrantError('Invalid grant: authorization code is invalid'); 95 | } 96 | 97 | if (!code.client) { 98 | throw new ServerError('Server error: `getAuthorizationCode()` did not return a `client` object'); 99 | } 100 | 101 | if (!code.user) { 102 | throw new ServerError('Server error: `getAuthorizationCode()` did not return a `user` object'); 103 | } 104 | 105 | if (code.client.id !== client.id) { 106 | throw new InvalidGrantError('Invalid grant: authorization code is invalid'); 107 | } 108 | 109 | if (!(code.expiresAt instanceof Date)) { 110 | throw new ServerError('Server error: `expiresAt` must be a Date instance'); 111 | } 112 | 113 | if (code.expiresAt < new Date()) { 114 | throw new InvalidGrantError('Invalid grant: authorization code has expired'); 115 | } 116 | 117 | if (code.redirectUri && !is.uri(code.redirectUri)) { 118 | throw new InvalidGrantError('Invalid grant: `redirect_uri` is not a valid URI'); 119 | } 120 | 121 | return code; 122 | }); 123 | }; 124 | 125 | /** 126 | * Validate the redirect URI. 127 | * 128 | * "The authorization server MUST ensure that the redirect_uri parameter is 129 | * present if the redirect_uri parameter was included in the initial 130 | * authorization request as described in Section 4.1.1, and if included 131 | * ensure that their values are identical." 132 | * 133 | * @see https://tools.ietf.org/html/rfc6749#section-4.1.3 134 | */ 135 | 136 | AuthorizationCodeGrantType.prototype.validateRedirectUri = function(request, code) { 137 | if (!code.redirectUri) { 138 | return; 139 | } 140 | 141 | var redirectUri = request.body.redirect_uri || request.query.redirect_uri; 142 | 143 | if (!is.uri(redirectUri)) { 144 | throw new InvalidRequestError('Invalid request: `redirect_uri` is not a valid URI'); 145 | } 146 | 147 | if (redirectUri !== code.redirectUri) { 148 | throw new InvalidRequestError('Invalid request: `redirect_uri` is invalid'); 149 | } 150 | }; 151 | 152 | /** 153 | * Revoke the authorization code. 154 | * 155 | * "The authorization code MUST expire shortly after it is issued to mitigate 156 | * the risk of leaks. [...] If an authorization code is used more than once, 157 | * the authorization server MUST deny the request." 158 | * 159 | * @see https://tools.ietf.org/html/rfc6749#section-4.1.2 160 | */ 161 | 162 | AuthorizationCodeGrantType.prototype.revokeAuthorizationCode = function(code) { 163 | return promisify(this.model.revokeAuthorizationCode, 1).call(this.model, code) 164 | .then(function(status) { 165 | if (!status) { 166 | throw new InvalidGrantError('Invalid grant: authorization code is invalid'); 167 | } 168 | 169 | return code; 170 | }); 171 | }; 172 | 173 | /** 174 | * Save token. 175 | */ 176 | 177 | AuthorizationCodeGrantType.prototype.saveToken = function(user, client, authorizationCode, scope) { 178 | var fns = [ 179 | this.validateScope(user, client, scope), 180 | this.generateAccessToken(client, user, scope), 181 | this.generateRefreshToken(client, user, scope), 182 | this.getAccessTokenExpiresAt(), 183 | this.getRefreshTokenExpiresAt() 184 | ]; 185 | 186 | return Promise.all(fns) 187 | .bind(this) 188 | .spread(function(scope, accessToken, refreshToken, accessTokenExpiresAt, refreshTokenExpiresAt) { 189 | var token = { 190 | accessToken: accessToken, 191 | authorizationCode: authorizationCode, 192 | accessTokenExpiresAt: accessTokenExpiresAt, 193 | refreshToken: refreshToken, 194 | refreshTokenExpiresAt: refreshTokenExpiresAt, 195 | scope: scope 196 | }; 197 | 198 | return promisify(this.model.saveToken, 3).call(this.model, token, client, user); 199 | }); 200 | }; 201 | 202 | /** 203 | * Export constructor. 204 | */ 205 | 206 | module.exports = AuthorizationCodeGrantType; 207 | -------------------------------------------------------------------------------- /lib/grant-types/client-credentials-grant-type.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var AbstractGrantType = require('./abstract-grant-type'); 8 | var InvalidArgumentError = require('../errors/invalid-argument-error'); 9 | var InvalidGrantError = require('../errors/invalid-grant-error'); 10 | var Promise = require('bluebird'); 11 | var promisify = require('promisify-any').use(Promise); 12 | var util = require('util'); 13 | 14 | /** 15 | * Constructor. 16 | */ 17 | 18 | function ClientCredentialsGrantType(options) { 19 | options = options || {}; 20 | 21 | if (!options.model) { 22 | throw new InvalidArgumentError('Missing parameter: `model`'); 23 | } 24 | 25 | if (!options.model.getUserFromClient) { 26 | throw new InvalidArgumentError('Invalid argument: model does not implement `getUserFromClient()`'); 27 | } 28 | 29 | if (!options.model.saveToken) { 30 | throw new InvalidArgumentError('Invalid argument: model does not implement `saveToken()`'); 31 | } 32 | 33 | AbstractGrantType.call(this, options); 34 | } 35 | 36 | /** 37 | * Inherit prototype. 38 | */ 39 | 40 | util.inherits(ClientCredentialsGrantType, AbstractGrantType); 41 | 42 | /** 43 | * Handle client credentials grant. 44 | * 45 | * @see https://tools.ietf.org/html/rfc6749#section-4.4.2 46 | */ 47 | 48 | ClientCredentialsGrantType.prototype.handle = function(request, client) { 49 | if (!request) { 50 | throw new InvalidArgumentError('Missing parameter: `request`'); 51 | } 52 | 53 | if (!client) { 54 | throw new InvalidArgumentError('Missing parameter: `client`'); 55 | } 56 | 57 | var scope = this.getScope(request); 58 | 59 | return Promise.bind(this) 60 | .then(function() { 61 | return this.getUserFromClient(client); 62 | }) 63 | .then(function(user) { 64 | return this.saveToken(user, client, scope); 65 | }); 66 | }; 67 | 68 | /** 69 | * Retrieve the user using client credentials. 70 | */ 71 | 72 | ClientCredentialsGrantType.prototype.getUserFromClient = function(client) { 73 | return promisify(this.model.getUserFromClient, 1).call(this.model, client) 74 | .then(function(user) { 75 | if (!user) { 76 | throw new InvalidGrantError('Invalid grant: user credentials are invalid'); 77 | } 78 | 79 | return user; 80 | }); 81 | }; 82 | 83 | /** 84 | * Save token. 85 | */ 86 | 87 | ClientCredentialsGrantType.prototype.saveToken = function(user, client, scope) { 88 | var fns = [ 89 | this.validateScope(user, client, scope), 90 | this.generateAccessToken(client, user, scope), 91 | this.getAccessTokenExpiresAt(client, user, scope) 92 | ]; 93 | 94 | return Promise.all(fns) 95 | .bind(this) 96 | .spread(function(scope, accessToken, accessTokenExpiresAt) { 97 | var token = { 98 | accessToken: accessToken, 99 | accessTokenExpiresAt: accessTokenExpiresAt, 100 | scope: scope 101 | }; 102 | 103 | return promisify(this.model.saveToken, 3).call(this.model, token, client, user); 104 | }); 105 | }; 106 | 107 | /** 108 | * Export constructor. 109 | */ 110 | 111 | module.exports = ClientCredentialsGrantType; 112 | -------------------------------------------------------------------------------- /lib/grant-types/password-grant-type.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var AbstractGrantType = require('./abstract-grant-type'); 8 | var InvalidArgumentError = require('../errors/invalid-argument-error'); 9 | var InvalidGrantError = require('../errors/invalid-grant-error'); 10 | var InvalidRequestError = require('../errors/invalid-request-error'); 11 | var Promise = require('bluebird'); 12 | var promisify = require('promisify-any').use(Promise); 13 | var is = require('../validator/is'); 14 | var util = require('util'); 15 | 16 | /** 17 | * Constructor. 18 | */ 19 | 20 | function PasswordGrantType(options) { 21 | options = options || {}; 22 | 23 | if (!options.model) { 24 | throw new InvalidArgumentError('Missing parameter: `model`'); 25 | } 26 | 27 | if (!options.model.getUser) { 28 | throw new InvalidArgumentError('Invalid argument: model does not implement `getUser()`'); 29 | } 30 | 31 | if (!options.model.saveToken) { 32 | throw new InvalidArgumentError('Invalid argument: model does not implement `saveToken()`'); 33 | } 34 | 35 | AbstractGrantType.call(this, options); 36 | } 37 | 38 | /** 39 | * Inherit prototype. 40 | */ 41 | 42 | util.inherits(PasswordGrantType, AbstractGrantType); 43 | 44 | /** 45 | * Retrieve the user from the model using a username/password combination. 46 | * 47 | * @see https://tools.ietf.org/html/rfc6749#section-4.3.2 48 | */ 49 | 50 | PasswordGrantType.prototype.handle = function(request, client) { 51 | if (!request) { 52 | throw new InvalidArgumentError('Missing parameter: `request`'); 53 | } 54 | 55 | if (!client) { 56 | throw new InvalidArgumentError('Missing parameter: `client`'); 57 | } 58 | 59 | var scope = this.getScope(request); 60 | 61 | return Promise.bind(this) 62 | .then(function() { 63 | return this.getUser(request); 64 | }) 65 | .then(function(user) { 66 | return this.saveToken(user, client, scope); 67 | }); 68 | }; 69 | 70 | /** 71 | * Get user using a username/password combination. 72 | */ 73 | 74 | PasswordGrantType.prototype.getUser = function(request) { 75 | if (!request.body.username) { 76 | throw new InvalidRequestError('Missing parameter: `username`'); 77 | } 78 | 79 | if (!request.body.password) { 80 | throw new InvalidRequestError('Missing parameter: `password`'); 81 | } 82 | 83 | if (!is.uchar(request.body.username)) { 84 | throw new InvalidRequestError('Invalid parameter: `username`'); 85 | } 86 | 87 | if (!is.uchar(request.body.password)) { 88 | throw new InvalidRequestError('Invalid parameter: `password`'); 89 | } 90 | 91 | return promisify(this.model.getUser, 2).call(this.model, request.body.username, request.body.password) 92 | .then(function(user) { 93 | if (!user) { 94 | throw new InvalidGrantError('Invalid grant: user credentials are invalid'); 95 | } 96 | 97 | return user; 98 | }); 99 | }; 100 | 101 | /** 102 | * Save token. 103 | */ 104 | 105 | PasswordGrantType.prototype.saveToken = function(user, client, scope) { 106 | var fns = [ 107 | this.validateScope(user, client, scope), 108 | this.generateAccessToken(client, user, scope), 109 | this.generateRefreshToken(client, user, scope), 110 | this.getAccessTokenExpiresAt(), 111 | this.getRefreshTokenExpiresAt() 112 | ]; 113 | 114 | return Promise.all(fns) 115 | .bind(this) 116 | .spread(function(scope, accessToken, refreshToken, accessTokenExpiresAt, refreshTokenExpiresAt) { 117 | var token = { 118 | accessToken: accessToken, 119 | accessTokenExpiresAt: accessTokenExpiresAt, 120 | refreshToken: refreshToken, 121 | refreshTokenExpiresAt: refreshTokenExpiresAt, 122 | scope: scope 123 | }; 124 | 125 | return promisify(this.model.saveToken, 3).call(this.model, token, client, user); 126 | }); 127 | }; 128 | 129 | /** 130 | * Export constructor. 131 | */ 132 | 133 | module.exports = PasswordGrantType; 134 | -------------------------------------------------------------------------------- /lib/grant-types/refresh-token-grant-type.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var AbstractGrantType = require('./abstract-grant-type'); 8 | var InvalidArgumentError = require('../errors/invalid-argument-error'); 9 | var InvalidGrantError = require('../errors/invalid-grant-error'); 10 | var InvalidRequestError = require('../errors/invalid-request-error'); 11 | var Promise = require('bluebird'); 12 | var promisify = require('promisify-any').use(Promise); 13 | var ServerError = require('../errors/server-error'); 14 | var is = require('../validator/is'); 15 | var util = require('util'); 16 | 17 | /** 18 | * Constructor. 19 | */ 20 | 21 | function RefreshTokenGrantType(options) { 22 | options = options || {}; 23 | 24 | if (!options.model) { 25 | throw new InvalidArgumentError('Missing parameter: `model`'); 26 | } 27 | 28 | if (!options.model.getRefreshToken) { 29 | throw new InvalidArgumentError('Invalid argument: model does not implement `getRefreshToken()`'); 30 | } 31 | 32 | if (!options.model.revokeToken) { 33 | throw new InvalidArgumentError('Invalid argument: model does not implement `revokeToken()`'); 34 | } 35 | 36 | if (!options.model.saveToken) { 37 | throw new InvalidArgumentError('Invalid argument: model does not implement `saveToken()`'); 38 | } 39 | 40 | AbstractGrantType.call(this, options); 41 | } 42 | 43 | /** 44 | * Inherit prototype. 45 | */ 46 | 47 | util.inherits(RefreshTokenGrantType, AbstractGrantType); 48 | 49 | /** 50 | * Handle refresh token grant. 51 | * 52 | * @see https://tools.ietf.org/html/rfc6749#section-6 53 | */ 54 | 55 | RefreshTokenGrantType.prototype.handle = function(request, client) { 56 | if (!request) { 57 | throw new InvalidArgumentError('Missing parameter: `request`'); 58 | } 59 | 60 | if (!client) { 61 | throw new InvalidArgumentError('Missing parameter: `client`'); 62 | } 63 | 64 | return Promise.bind(this) 65 | .then(function() { 66 | return this.getRefreshToken(request, client); 67 | }) 68 | .tap(function(token) { 69 | return this.revokeToken(token); 70 | }) 71 | .then(function(token) { 72 | return this.saveToken(token.user, client, token.scope); 73 | }); 74 | }; 75 | 76 | /** 77 | * Get refresh token. 78 | */ 79 | 80 | RefreshTokenGrantType.prototype.getRefreshToken = function(request, client) { 81 | if (!request.body.refresh_token) { 82 | throw new InvalidRequestError('Missing parameter: `refresh_token`'); 83 | } 84 | 85 | if (!is.vschar(request.body.refresh_token)) { 86 | throw new InvalidRequestError('Invalid parameter: `refresh_token`'); 87 | } 88 | 89 | return promisify(this.model.getRefreshToken, 1).call(this.model, request.body.refresh_token) 90 | .then(function(token) { 91 | if (!token) { 92 | throw new InvalidGrantError('Invalid grant: refresh token is invalid'); 93 | } 94 | 95 | if (!token.client) { 96 | throw new ServerError('Server error: `getRefreshToken()` did not return a `client` object'); 97 | } 98 | 99 | if (!token.user) { 100 | throw new ServerError('Server error: `getRefreshToken()` did not return a `user` object'); 101 | } 102 | 103 | if (token.client.id !== client.id) { 104 | throw new InvalidGrantError('Invalid grant: refresh token is invalid'); 105 | } 106 | 107 | if (token.refreshTokenExpiresAt && !(token.refreshTokenExpiresAt instanceof Date)) { 108 | throw new ServerError('Server error: `refreshTokenExpiresAt` must be a Date instance'); 109 | } 110 | 111 | if (token.refreshTokenExpiresAt && token.refreshTokenExpiresAt < new Date()) { 112 | throw new InvalidGrantError('Invalid grant: refresh token has expired'); 113 | } 114 | 115 | return token; 116 | }); 117 | }; 118 | 119 | /** 120 | * Revoke the refresh token. 121 | * 122 | * @see https://tools.ietf.org/html/rfc6749#section-6 123 | */ 124 | 125 | RefreshTokenGrantType.prototype.revokeToken = function(token) { 126 | if (this.alwaysIssueNewRefreshToken === false) { 127 | return Promise.resolve(token); 128 | } 129 | 130 | return promisify(this.model.revokeToken, 1).call(this.model, token) 131 | .then(function(status) { 132 | if (!status) { 133 | throw new InvalidGrantError('Invalid grant: refresh token is invalid'); 134 | } 135 | 136 | return token; 137 | }); 138 | }; 139 | 140 | /** 141 | * Save token. 142 | */ 143 | 144 | RefreshTokenGrantType.prototype.saveToken = function(user, client, scope) { 145 | var fns = [ 146 | this.generateAccessToken(client, user, scope), 147 | this.generateRefreshToken(client, user, scope), 148 | this.getAccessTokenExpiresAt(), 149 | this.getRefreshTokenExpiresAt() 150 | ]; 151 | 152 | return Promise.all(fns) 153 | .bind(this) 154 | .spread(function(accessToken, refreshToken, accessTokenExpiresAt, refreshTokenExpiresAt) { 155 | var token = { 156 | accessToken: accessToken, 157 | accessTokenExpiresAt: accessTokenExpiresAt, 158 | scope: scope 159 | }; 160 | 161 | if (this.alwaysIssueNewRefreshToken !== false) { 162 | token.refreshToken = refreshToken; 163 | token.refreshTokenExpiresAt = refreshTokenExpiresAt; 164 | } 165 | 166 | return token; 167 | }) 168 | .then(function(token) { 169 | return promisify(this.model.saveToken, 3).call(this.model, token, client, user) 170 | .then(function(savedToken) { 171 | return savedToken; 172 | }); 173 | }); 174 | }; 175 | 176 | /** 177 | * Export constructor. 178 | */ 179 | 180 | module.exports = RefreshTokenGrantType; 181 | -------------------------------------------------------------------------------- /lib/handlers/authenticate-handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var InvalidArgumentError = require('../errors/invalid-argument-error'); 8 | var InvalidRequestError = require('../errors/invalid-request-error'); 9 | var InsufficientScopeError = require('../errors/insufficient-scope-error'); 10 | var InvalidTokenError = require('../errors/invalid-token-error'); 11 | var OAuthError = require('../errors/oauth-error'); 12 | var Promise = require('bluebird'); 13 | var promisify = require('promisify-any').use(Promise); 14 | var Request = require('../request'); 15 | var Response = require('../response'); 16 | var ServerError = require('../errors/server-error'); 17 | var UnauthorizedRequestError = require('../errors/unauthorized-request-error'); 18 | 19 | /** 20 | * Constructor. 21 | */ 22 | 23 | function AuthenticateHandler(options) { 24 | options = options || {}; 25 | 26 | if (!options.model) { 27 | throw new InvalidArgumentError('Missing parameter: `model`'); 28 | } 29 | 30 | if (!options.model.getAccessToken) { 31 | throw new InvalidArgumentError('Invalid argument: model does not implement `getAccessToken()`'); 32 | } 33 | 34 | if (options.scope && undefined === options.addAcceptedScopesHeader) { 35 | throw new InvalidArgumentError('Missing parameter: `addAcceptedScopesHeader`'); 36 | } 37 | 38 | if (options.scope && undefined === options.addAuthorizedScopesHeader) { 39 | throw new InvalidArgumentError('Missing parameter: `addAuthorizedScopesHeader`'); 40 | } 41 | 42 | if (options.scope && !options.model.verifyScope) { 43 | throw new InvalidArgumentError('Invalid argument: model does not implement `verifyScope()`'); 44 | } 45 | 46 | this.addAcceptedScopesHeader = options.addAcceptedScopesHeader; 47 | this.addAuthorizedScopesHeader = options.addAuthorizedScopesHeader; 48 | this.allowBearerTokensInQueryString = options.allowBearerTokensInQueryString; 49 | this.model = options.model; 50 | this.scope = options.scope; 51 | } 52 | 53 | /** 54 | * Authenticate Handler. 55 | */ 56 | 57 | AuthenticateHandler.prototype.handle = function(request, response) { 58 | if (!(request instanceof Request)) { 59 | throw new InvalidArgumentError('Invalid argument: `request` must be an instance of Request'); 60 | } 61 | 62 | if (!(response instanceof Response)) { 63 | throw new InvalidArgumentError('Invalid argument: `response` must be an instance of Response'); 64 | } 65 | 66 | return Promise.bind(this) 67 | .then(function() { 68 | return this.getTokenFromRequest(request); 69 | }) 70 | .then(function(token) { 71 | return this.getAccessToken(token); 72 | }) 73 | .tap(function(token) { 74 | return this.validateAccessToken(token); 75 | }) 76 | .tap(function(token) { 77 | if (!this.scope) { 78 | return; 79 | } 80 | 81 | return this.verifyScope(token); 82 | }) 83 | .tap(function(token) { 84 | return this.updateResponse(response, token); 85 | }) 86 | .catch(function(e) { 87 | // Include the "WWW-Authenticate" response header field if the client 88 | // lacks any authentication information. 89 | // 90 | // @see https://tools.ietf.org/html/rfc6750#section-3.1 91 | if (e instanceof UnauthorizedRequestError) { 92 | response.set('WWW-Authenticate', 'Bearer realm="Service"'); 93 | } 94 | 95 | if (!(e instanceof OAuthError)) { 96 | throw new ServerError(e); 97 | } 98 | 99 | throw e; 100 | }); 101 | }; 102 | 103 | /** 104 | * Get the token from the header or body, depending on the request. 105 | * 106 | * "Clients MUST NOT use more than one method to transmit the token in each request." 107 | * 108 | * @see https://tools.ietf.org/html/rfc6750#section-2 109 | */ 110 | 111 | AuthenticateHandler.prototype.getTokenFromRequest = function(request) { 112 | var headerToken = request.get('Authorization'); 113 | var queryToken = request.query.access_token; 114 | var bodyToken = request.body.access_token; 115 | 116 | if (!!headerToken + !!queryToken + !!bodyToken > 1) { 117 | throw new InvalidRequestError('Invalid request: only one authentication method is allowed'); 118 | } 119 | 120 | if (headerToken) { 121 | return this.getTokenFromRequestHeader(request); 122 | } 123 | 124 | if (queryToken) { 125 | return this.getTokenFromRequestQuery(request); 126 | } 127 | 128 | if (bodyToken) { 129 | return this.getTokenFromRequestBody(request); 130 | } 131 | 132 | throw new UnauthorizedRequestError('Unauthorized request: no authentication given'); 133 | }; 134 | 135 | /** 136 | * Get the token from the request header. 137 | * 138 | * @see http://tools.ietf.org/html/rfc6750#section-2.1 139 | */ 140 | 141 | AuthenticateHandler.prototype.getTokenFromRequestHeader = function(request) { 142 | var token = request.get('Authorization'); 143 | var matches = token.match(/Bearer\s(\S+)/); 144 | 145 | if (!matches) { 146 | throw new InvalidRequestError('Invalid request: malformed authorization header'); 147 | } 148 | 149 | return matches[1]; 150 | }; 151 | 152 | /** 153 | * Get the token from the request query. 154 | * 155 | * "Don't pass bearer tokens in page URLs: Bearer tokens SHOULD NOT be passed in page 156 | * URLs (for example, as query string parameters). Instead, bearer tokens SHOULD be 157 | * passed in HTTP message headers or message bodies for which confidentiality measures 158 | * are taken. Browsers, web servers, and other software may not adequately secure URLs 159 | * in the browser history, web server logs, and other data structures. If bearer tokens 160 | * are passed in page URLs, attackers might be able to steal them from the history data, 161 | * logs, or other unsecured locations." 162 | * 163 | * @see http://tools.ietf.org/html/rfc6750#section-2.3 164 | */ 165 | 166 | AuthenticateHandler.prototype.getTokenFromRequestQuery = function(request) { 167 | if (!this.allowBearerTokensInQueryString) { 168 | throw new InvalidRequestError('Invalid request: do not send bearer tokens in query URLs'); 169 | } 170 | 171 | return request.query.access_token; 172 | }; 173 | 174 | /** 175 | * Get the token from the request body. 176 | * 177 | * "The HTTP request method is one for which the request-body has defined semantics. 178 | * In particular, this means that the "GET" method MUST NOT be used." 179 | * 180 | * @see http://tools.ietf.org/html/rfc6750#section-2.2 181 | */ 182 | 183 | AuthenticateHandler.prototype.getTokenFromRequestBody = function(request) { 184 | if (request.method === 'GET') { 185 | throw new InvalidRequestError('Invalid request: token may not be passed in the body when using the GET verb'); 186 | } 187 | 188 | if (!request.is('application/x-www-form-urlencoded')) { 189 | throw new InvalidRequestError('Invalid request: content must be application/x-www-form-urlencoded'); 190 | } 191 | 192 | return request.body.access_token; 193 | }; 194 | 195 | /** 196 | * Get the access token from the model. 197 | */ 198 | 199 | AuthenticateHandler.prototype.getAccessToken = function(token) { 200 | return promisify(this.model.getAccessToken, 1).call(this.model, token) 201 | .then(function(accessToken) { 202 | if (!accessToken) { 203 | throw new InvalidTokenError('Invalid token: access token is invalid'); 204 | } 205 | 206 | if (!accessToken.user) { 207 | throw new ServerError('Server error: `getAccessToken()` did not return a `user` object'); 208 | } 209 | 210 | return accessToken; 211 | }); 212 | }; 213 | 214 | /** 215 | * Validate access token. 216 | */ 217 | 218 | AuthenticateHandler.prototype.validateAccessToken = function(accessToken) { 219 | if (!(accessToken.accessTokenExpiresAt instanceof Date)) { 220 | throw new ServerError('Server error: `accessTokenExpiresAt` must be a Date instance'); 221 | } 222 | 223 | if (accessToken.accessTokenExpiresAt < new Date()) { 224 | throw new InvalidTokenError('Invalid token: access token has expired'); 225 | } 226 | 227 | return accessToken; 228 | }; 229 | 230 | /** 231 | * Verify scope. 232 | */ 233 | 234 | AuthenticateHandler.prototype.verifyScope = function(accessToken) { 235 | return promisify(this.model.verifyScope, 2).call(this.model, accessToken, this.scope) 236 | .then(function(scope) { 237 | if (!scope) { 238 | throw new InsufficientScopeError('Insufficient scope: authorized scope is insufficient'); 239 | } 240 | 241 | return scope; 242 | }); 243 | }; 244 | 245 | /** 246 | * Update response. 247 | */ 248 | 249 | AuthenticateHandler.prototype.updateResponse = function(response, accessToken) { 250 | if (this.scope && this.addAcceptedScopesHeader) { 251 | response.set('X-Accepted-OAuth-Scopes', this.scope); 252 | } 253 | 254 | if (this.scope && this.addAuthorizedScopesHeader) { 255 | response.set('X-OAuth-Scopes', accessToken.scope); 256 | } 257 | }; 258 | 259 | /** 260 | * Export constructor. 261 | */ 262 | 263 | module.exports = AuthenticateHandler; 264 | -------------------------------------------------------------------------------- /lib/models/token-model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var InvalidArgumentError = require('../errors/invalid-argument-error'); 8 | 9 | /** 10 | * Constructor. 11 | */ 12 | 13 | var modelAttributes = ['accessToken', 'accessTokenExpiresAt', 'refreshToken', 'refreshTokenExpiresAt', 'scope', 'client', 'user']; 14 | 15 | function TokenModel(data, options) { 16 | data = data || {}; 17 | 18 | if (!data.accessToken) { 19 | throw new InvalidArgumentError('Missing parameter: `accessToken`'); 20 | } 21 | 22 | if (!data.client) { 23 | throw new InvalidArgumentError('Missing parameter: `client`'); 24 | } 25 | 26 | if (!data.user) { 27 | throw new InvalidArgumentError('Missing parameter: `user`'); 28 | } 29 | 30 | if (data.accessTokenExpiresAt && !(data.accessTokenExpiresAt instanceof Date)) { 31 | throw new InvalidArgumentError('Invalid parameter: `accessTokenExpiresAt`'); 32 | } 33 | 34 | if (data.refreshTokenExpiresAt && !(data.refreshTokenExpiresAt instanceof Date)) { 35 | throw new InvalidArgumentError('Invalid parameter: `refreshTokenExpiresAt`'); 36 | } 37 | 38 | this.accessToken = data.accessToken; 39 | this.accessTokenExpiresAt = data.accessTokenExpiresAt; 40 | this.client = data.client; 41 | this.refreshToken = data.refreshToken; 42 | this.refreshTokenExpiresAt = data.refreshTokenExpiresAt; 43 | this.scope = data.scope; 44 | this.user = data.user; 45 | 46 | if (options && options.allowExtendedTokenAttributes) { 47 | this.customAttributes = {}; 48 | 49 | for (var key in data) { 50 | if (data.hasOwnProperty(key) && (modelAttributes.indexOf(key) < 0)) { 51 | this.customAttributes[key] = data[key]; 52 | } 53 | } 54 | } 55 | 56 | if(this.accessTokenExpiresAt) { 57 | this.accessTokenLifetime = Math.floor((this.accessTokenExpiresAt - new Date()) / 1000); 58 | } 59 | } 60 | 61 | /** 62 | * Export constructor. 63 | */ 64 | 65 | module.exports = TokenModel; 66 | -------------------------------------------------------------------------------- /lib/request.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var InvalidArgumentError = require('./errors/invalid-argument-error'); 8 | var typeis = require('type-is'); 9 | 10 | /** 11 | * Constructor. 12 | */ 13 | 14 | function Request(options) { 15 | options = options || {}; 16 | 17 | if (!options.headers) { 18 | throw new InvalidArgumentError('Missing parameter: `headers`'); 19 | } 20 | 21 | if (!options.method) { 22 | throw new InvalidArgumentError('Missing parameter: `method`'); 23 | } 24 | 25 | if (!options.query) { 26 | throw new InvalidArgumentError('Missing parameter: `query`'); 27 | } 28 | 29 | this.body = options.body || {}; 30 | this.headers = {}; 31 | this.method = options.method; 32 | this.query = options.query; 33 | 34 | // Store the headers in lower case. 35 | for (var field in options.headers) { 36 | if (Object.prototype.hasOwnProperty.call(options.headers, field)) { 37 | this.headers[field.toLowerCase()] = options.headers[field]; 38 | } 39 | } 40 | 41 | // Store additional properties of the request object passed in 42 | for (var property in options) { 43 | if (Object.prototype.hasOwnProperty.call(options, property) && !this[property]) { 44 | this[property] = options[property]; 45 | } 46 | } 47 | } 48 | 49 | /** 50 | * Get a request header. 51 | */ 52 | 53 | Request.prototype.get = function(field) { 54 | return this.headers[field.toLowerCase()]; 55 | }; 56 | 57 | /** 58 | * Check if the content-type matches any of the given mime type. 59 | */ 60 | 61 | Request.prototype.is = function(types) { 62 | if (!Array.isArray(types)) { 63 | types = [].slice.call(arguments); 64 | } 65 | 66 | return typeis(this, types) || false; 67 | }; 68 | 69 | /** 70 | * Export constructor. 71 | */ 72 | 73 | module.exports = Request; 74 | -------------------------------------------------------------------------------- /lib/response-types/code-response-type.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var InvalidArgumentError = require('../errors/invalid-argument-error'); 8 | var url = require('url'); 9 | 10 | /** 11 | * Constructor. 12 | */ 13 | 14 | function CodeResponseType(code) { 15 | if (!code) { 16 | throw new InvalidArgumentError('Missing parameter: `code`'); 17 | } 18 | 19 | this.code = code; 20 | } 21 | 22 | /** 23 | * Build redirect uri. 24 | */ 25 | 26 | CodeResponseType.prototype.buildRedirectUri = function(redirectUri) { 27 | if (!redirectUri) { 28 | throw new InvalidArgumentError('Missing parameter: `redirectUri`'); 29 | } 30 | 31 | var uri = url.parse(redirectUri, true); 32 | 33 | uri.query.code = this.code; 34 | uri.search = null; 35 | 36 | return uri; 37 | }; 38 | 39 | /** 40 | * Export constructor. 41 | */ 42 | 43 | module.exports = CodeResponseType; 44 | -------------------------------------------------------------------------------- /lib/response-types/token-response-type.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var ServerError = require('../errors/server-error'); 8 | 9 | /** 10 | * Constructor. 11 | */ 12 | 13 | function TokenResponseType() { 14 | throw new ServerError('Not implemented.'); 15 | } 16 | 17 | /** 18 | * Export constructor. 19 | */ 20 | 21 | module.exports = TokenResponseType; 22 | -------------------------------------------------------------------------------- /lib/response.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Constructor. 5 | */ 6 | 7 | function Response(options) { 8 | options = options || {}; 9 | 10 | this.body = options.body || {}; 11 | this.headers = {}; 12 | this.status = 200; 13 | 14 | // Store the headers in lower case. 15 | for (var field in options.headers) { 16 | if (Object.prototype.hasOwnProperty.call(options.headers, field)) { 17 | this.headers[field.toLowerCase()] = options.headers[field]; 18 | } 19 | } 20 | 21 | // Store additional properties of the response object passed in 22 | for (var property in options) { 23 | if (Object.prototype.hasOwnProperty.call(options, property) && !this[property]) { 24 | this[property] = options[property]; 25 | } 26 | } 27 | } 28 | 29 | /** 30 | * Get a response header. 31 | */ 32 | 33 | Response.prototype.get = function(field) { 34 | return this.headers[field.toLowerCase()]; 35 | }; 36 | 37 | /** 38 | * Redirect response. 39 | */ 40 | 41 | Response.prototype.redirect = function(url) { 42 | this.set('Location', url); 43 | this.status = 302; 44 | }; 45 | 46 | /** 47 | * Set a response header. 48 | */ 49 | 50 | Response.prototype.set = function(field, value) { 51 | this.headers[field.toLowerCase()] = value; 52 | }; 53 | 54 | /** 55 | * Export constructor. 56 | */ 57 | 58 | module.exports = Response; 59 | -------------------------------------------------------------------------------- /lib/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var _ = require('lodash'); 8 | var AuthenticateHandler = require('./handlers/authenticate-handler'); 9 | var AuthorizeHandler = require('./handlers/authorize-handler'); 10 | var InvalidArgumentError = require('./errors/invalid-argument-error'); 11 | var TokenHandler = require('./handlers/token-handler'); 12 | 13 | /** 14 | * Constructor. 15 | */ 16 | 17 | function OAuth2Server(options) { 18 | options = options || {}; 19 | 20 | if (!options.model) { 21 | throw new InvalidArgumentError('Missing parameter: `model`'); 22 | } 23 | 24 | this.options = options; 25 | } 26 | 27 | /** 28 | * Authenticate a token. 29 | */ 30 | 31 | OAuth2Server.prototype.authenticate = function(request, response, options, callback) { 32 | if (typeof options === 'string') { 33 | options = {scope: options}; 34 | } 35 | 36 | options = _.assign({ 37 | addAcceptedScopesHeader: true, 38 | addAuthorizedScopesHeader: true, 39 | allowBearerTokensInQueryString: false 40 | }, this.options, options); 41 | 42 | return new AuthenticateHandler(options) 43 | .handle(request, response) 44 | .nodeify(callback); 45 | }; 46 | 47 | /** 48 | * Authorize a request. 49 | */ 50 | 51 | OAuth2Server.prototype.authorize = function(request, response, options, callback) { 52 | options = _.assign({ 53 | allowEmptyState: false, 54 | authorizationCodeLifetime: 5 * 60 // 5 minutes. 55 | }, this.options, options); 56 | 57 | return new AuthorizeHandler(options) 58 | .handle(request, response) 59 | .nodeify(callback); 60 | }; 61 | 62 | /** 63 | * Create a token. 64 | */ 65 | 66 | OAuth2Server.prototype.token = function(request, response, options, callback) { 67 | options = _.assign({ 68 | accessTokenLifetime: 60 * 60, // 1 hour. 69 | refreshTokenLifetime: 60 * 60 * 24 * 14, // 2 weeks. 70 | allowExtendedTokenAttributes: false, 71 | requireClientAuthentication: {} // defaults to true for all grant types 72 | }, this.options, options); 73 | 74 | return new TokenHandler(options) 75 | .handle(request, response) 76 | .nodeify(callback); 77 | }; 78 | 79 | /** 80 | * Export constructor. 81 | */ 82 | 83 | module.exports = OAuth2Server; 84 | -------------------------------------------------------------------------------- /lib/token-types/bearer-token-type.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var InvalidArgumentError = require('../errors/invalid-argument-error'); 8 | 9 | /** 10 | * Constructor. 11 | */ 12 | 13 | function BearerTokenType(accessToken, accessTokenLifetime, refreshToken, scope, customAttributes) { 14 | if (!accessToken) { 15 | throw new InvalidArgumentError('Missing parameter: `accessToken`'); 16 | } 17 | 18 | this.accessToken = accessToken; 19 | this.accessTokenLifetime = accessTokenLifetime; 20 | this.refreshToken = refreshToken; 21 | this.scope = scope; 22 | 23 | if (customAttributes) { 24 | this.customAttributes = customAttributes; 25 | } 26 | } 27 | 28 | /** 29 | * Retrieve the value representation. 30 | */ 31 | 32 | BearerTokenType.prototype.valueOf = function() { 33 | var object = { 34 | access_token: this.accessToken, 35 | token_type: 'Bearer' 36 | }; 37 | 38 | if (this.accessTokenLifetime) { 39 | object.expires_in = this.accessTokenLifetime; 40 | } 41 | 42 | if (this.refreshToken) { 43 | object.refresh_token = this.refreshToken; 44 | } 45 | 46 | if (this.scope) { 47 | object.scope = this.scope; 48 | } 49 | 50 | for (var key in this.customAttributes) { 51 | if (this.customAttributes.hasOwnProperty(key)) { 52 | object[key] = this.customAttributes[key]; 53 | } 54 | } 55 | return object; 56 | }; 57 | 58 | /** 59 | * Export constructor. 60 | */ 61 | 62 | module.exports = BearerTokenType; 63 | -------------------------------------------------------------------------------- /lib/token-types/mac-token-type.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var ServerError = require('../errors/server-error'); 8 | 9 | /** 10 | * Constructor. 11 | */ 12 | 13 | function MacTokenType() { 14 | throw new ServerError('Not implemented.'); 15 | } 16 | 17 | /** 18 | * Export constructor. 19 | */ 20 | 21 | module.exports = MacTokenType; 22 | -------------------------------------------------------------------------------- /lib/utils/token-util.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var crypto = require('crypto'); 8 | var randomBytes = require('bluebird').promisify(require('crypto').randomBytes); 9 | 10 | /** 11 | * Export `TokenUtil`. 12 | */ 13 | 14 | module.exports = { 15 | 16 | /** 17 | * Generate random token. 18 | */ 19 | 20 | generateRandomToken: function() { 21 | return randomBytes(256).then(function(buffer) { 22 | return crypto 23 | .createHash('sha1') 24 | .update(buffer) 25 | .digest('hex'); 26 | }); 27 | } 28 | 29 | }; 30 | -------------------------------------------------------------------------------- /lib/validator/is.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Validation rules. 5 | */ 6 | 7 | var rules = { 8 | NCHAR: /^[\u002D|\u002E|\u005F|\w]+$/, 9 | NQCHAR: /^[\u0021|\u0023-\u005B|\u005D-\u007E]+$/, 10 | NQSCHAR: /^[\u0020-\u0021|\u0023-\u005B|\u005D-\u007E]+$/, 11 | UNICODECHARNOCRLF: /^[\u0009|\u0020-\u007E|\u0080-\uD7FF|\uE000-\uFFFD|\u10000-\u10FFFF]+$/, 12 | URI: /^[a-zA-Z][a-zA-Z0-9+.-]+:/, 13 | VSCHAR: /^[\u0020-\u007E]+$/ 14 | }; 15 | 16 | /** 17 | * Export validation functions. 18 | */ 19 | 20 | module.exports = { 21 | 22 | /** 23 | * Validate if a value matches a unicode character. 24 | * 25 | * @see https://tools.ietf.org/html/rfc6749#appendix-A 26 | */ 27 | 28 | nchar: function(value) { 29 | return rules.NCHAR.test(value); 30 | }, 31 | 32 | /** 33 | * Validate if a value matches a unicode character, including exclamation marks. 34 | * 35 | * @see https://tools.ietf.org/html/rfc6749#appendix-A 36 | */ 37 | 38 | nqchar: function(value) { 39 | return rules.NQCHAR.test(value); 40 | }, 41 | 42 | /** 43 | * Validate if a value matches a unicode character, including exclamation marks and spaces. 44 | * 45 | * @see https://tools.ietf.org/html/rfc6749#appendix-A 46 | */ 47 | 48 | nqschar: function(value) { 49 | return rules.NQSCHAR.test(value); 50 | }, 51 | 52 | /** 53 | * Validate if a value matches a unicode character excluding the carriage 54 | * return and linefeed characters. 55 | * 56 | * @see https://tools.ietf.org/html/rfc6749#appendix-A 57 | */ 58 | 59 | uchar: function(value) { 60 | return rules.UNICODECHARNOCRLF.test(value); 61 | }, 62 | 63 | /** 64 | * Validate if a value matches generic URIs. 65 | * 66 | * @see http://tools.ietf.org/html/rfc3986#section-3 67 | */ 68 | uri: function(value) { 69 | return rules.URI.test(value); 70 | }, 71 | 72 | /** 73 | * Validate if a value matches against the printable set of unicode characters. 74 | * 75 | * @see https://tools.ietf.org/html/rfc6749#appendix-A 76 | */ 77 | 78 | vschar: function(value) { 79 | return rules.VSCHAR.test(value); 80 | } 81 | }; 82 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oauth2-server", 3 | "description": "Complete, framework-agnostic, compliant and well tested module for implementing an OAuth2 Server in node.js", 4 | "version": "4.0.0-dev.3", 5 | "keywords": [ 6 | "oauth", 7 | "oauth2" 8 | ], 9 | "contributors": [ 10 | { 11 | "name": "Thom Seddon", 12 | "email": "thom@seddonmedia.co.uk" 13 | }, 14 | { 15 | "name": "Lars F. Karlström", 16 | "email": "lars@lfk.io" 17 | }, 18 | { 19 | "name": "Rui Marinho", 20 | "email": "ruipmarinho@gmail.com" 21 | }, 22 | { 23 | "name": "Tiago Ribeiro", 24 | "email": "tiago.ribeiro@gmail.com" 25 | }, 26 | { 27 | "name": "Michael Salinger", 28 | "email": "mjsalinger@gmail.com" 29 | }, 30 | { 31 | "name": "Nuno Sousa" 32 | }, 33 | { 34 | "name": "Max Truxa" 35 | } 36 | ], 37 | "main": "index.js", 38 | "dependencies": { 39 | "basic-auth": "2.0.1", 40 | "bluebird": "3.7.2", 41 | "lodash": "4.17.21", 42 | "promisify-any": "2.0.1", 43 | "statuses": "1.5.0", 44 | "type-is": "1.6.18" 45 | }, 46 | "devDependencies": { 47 | "jshint": "2.13.0", 48 | "mocha": "5.2.0", 49 | "should": "13.2.3", 50 | "sinon": "7.5.0" 51 | }, 52 | "license": "MIT", 53 | "engines": { 54 | "node": ">=4.0" 55 | }, 56 | "scripts": { 57 | "pretest": "./node_modules/.bin/jshint --config ./.jshintrc lib test", 58 | "test": "NODE_ENV=test ./node_modules/.bin/mocha 'test/**/*_test.js'", 59 | "test-debug": "NODE_ENV=test ./node_modules/.bin/mocha --inspect --debug-brk 'test/**/*_test.js'" 60 | }, 61 | "repository": { 62 | "type": "git", 63 | "url": "https://github.com/oauthjs/node-oauth2-server.git" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /test/assertions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var should = require('should'); 8 | 9 | /** 10 | * SHA-1 assertion. 11 | */ 12 | 13 | should.Assertion.add('sha1', function() { 14 | this.params = { operator: 'to be a valid SHA-1 hash' }; 15 | 16 | this.obj.should.match(/^[a-f0-9]{40}$/i); 17 | }, true); 18 | -------------------------------------------------------------------------------- /test/integration/grant-types/abstract-grant-type_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var AbstractGrantType = require('../../../lib/grant-types/abstract-grant-type'); 8 | var InvalidArgumentError = require('../../../lib/errors/invalid-argument-error'); 9 | var Promise = require('bluebird'); 10 | var Request = require('../../../lib/request'); 11 | var should = require('should'); 12 | 13 | /** 14 | * Test `AbstractGrantType` integration. 15 | */ 16 | 17 | describe('AbstractGrantType integration', function() { 18 | describe('constructor()', function() { 19 | it('should throw an error if `options.accessTokenLifetime` is missing', function() { 20 | try { 21 | new AbstractGrantType(); 22 | 23 | should.fail(); 24 | } catch (e) { 25 | e.should.be.an.instanceOf(InvalidArgumentError); 26 | e.message.should.equal('Missing parameter: `accessTokenLifetime`'); 27 | } 28 | }); 29 | 30 | it('should throw an error if `options.model` is missing', function() { 31 | try { 32 | new AbstractGrantType({ accessTokenLifetime: 123 }); 33 | 34 | should.fail(); 35 | } catch (e) { 36 | e.should.be.an.instanceOf(InvalidArgumentError); 37 | e.message.should.equal('Missing parameter: `model`'); 38 | } 39 | }); 40 | 41 | it('should set the `accessTokenLifetime`', function() { 42 | var grantType = new AbstractGrantType({ accessTokenLifetime: 123, model: {} }); 43 | 44 | grantType.accessTokenLifetime.should.equal(123); 45 | }); 46 | 47 | it('should set the `model`', function() { 48 | var model = {}; 49 | var grantType = new AbstractGrantType({ accessTokenLifetime: 123, model: model }); 50 | 51 | grantType.model.should.equal(model); 52 | }); 53 | 54 | it('should set the `refreshTokenLifetime`', function() { 55 | var grantType = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); 56 | 57 | grantType.refreshTokenLifetime.should.equal(456); 58 | }); 59 | }); 60 | 61 | describe('generateAccessToken()', function() { 62 | it('should return an access token', function() { 63 | var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); 64 | 65 | return handler.generateAccessToken() 66 | .then(function(data) { 67 | data.should.be.a.sha1; 68 | }) 69 | .catch(should.fail); 70 | }); 71 | 72 | it('should support promises', function() { 73 | var model = { 74 | generateAccessToken: function() { 75 | return Promise.resolve({}); 76 | } 77 | }; 78 | var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: model, refreshTokenLifetime: 456 }); 79 | 80 | handler.generateAccessToken().should.be.an.instanceOf(Promise); 81 | }); 82 | 83 | it('should support non-promises', function() { 84 | var model = { 85 | generateAccessToken: function() { 86 | return {}; 87 | } 88 | }; 89 | var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: model, refreshTokenLifetime: 456 }); 90 | 91 | handler.generateAccessToken().should.be.an.instanceOf(Promise); 92 | }); 93 | }); 94 | 95 | describe('generateRefreshToken()', function() { 96 | it('should return a refresh token', function() { 97 | var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); 98 | 99 | return handler.generateRefreshToken() 100 | .then(function(data) { 101 | data.should.be.a.sha1; 102 | }) 103 | .catch(should.fail); 104 | }); 105 | 106 | it('should support promises', function() { 107 | var model = { 108 | generateRefreshToken: function() { 109 | return Promise.resolve({}); 110 | } 111 | }; 112 | var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: model, refreshTokenLifetime: 456 }); 113 | 114 | handler.generateRefreshToken().should.be.an.instanceOf(Promise); 115 | }); 116 | 117 | it('should support non-promises', function() { 118 | var model = { 119 | generateRefreshToken: function() { 120 | return {}; 121 | } 122 | }; 123 | var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: model, refreshTokenLifetime: 456 }); 124 | 125 | handler.generateRefreshToken().should.be.an.instanceOf(Promise); 126 | }); 127 | }); 128 | 129 | describe('getAccessTokenExpiresAt()', function() { 130 | it('should return a date', function() { 131 | var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); 132 | 133 | handler.getAccessTokenExpiresAt().should.be.an.instanceOf(Date); 134 | }); 135 | }); 136 | 137 | describe('getRefreshTokenExpiresAt()', function() { 138 | it('should return a refresh token', function() { 139 | var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); 140 | 141 | handler.getRefreshTokenExpiresAt().should.be.an.instanceOf(Date); 142 | }); 143 | }); 144 | 145 | describe('getScope()', function() { 146 | it('should throw an error if `scope` is invalid', function() { 147 | var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); 148 | var request = new Request({ body: { scope: 'øå€£‰' }, headers: {}, method: {}, query: {} }); 149 | 150 | try { 151 | handler.getScope(request); 152 | 153 | should.fail(); 154 | } catch (e) { 155 | e.should.be.an.instanceOf(InvalidArgumentError); 156 | e.message.should.equal('Invalid parameter: `scope`'); 157 | } 158 | }); 159 | 160 | it('should allow the `scope` to be `undefined`', function() { 161 | var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); 162 | var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); 163 | 164 | should.not.exist(handler.getScope(request)); 165 | }); 166 | 167 | it('should return the scope', function() { 168 | var handler = new AbstractGrantType({ accessTokenLifetime: 123, model: {}, refreshTokenLifetime: 456 }); 169 | var request = new Request({ body: { scope: 'foo' }, headers: {}, method: {}, query: {} }); 170 | 171 | handler.getScope(request).should.equal('foo'); 172 | }); 173 | }); 174 | }); 175 | -------------------------------------------------------------------------------- /test/integration/request_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var Request = require('../../lib/request'); 8 | var InvalidArgumentError = require('../../lib/errors/invalid-argument-error'); 9 | var should = require('should'); 10 | 11 | /** 12 | * Test `Request` integration. 13 | */ 14 | 15 | describe('Request integration', function() { 16 | describe('constructor()', function() { 17 | it('should throw an error if `headers` is missing', function() { 18 | try { 19 | new Request({ body: {} }); 20 | 21 | should.fail(); 22 | } catch (e) { 23 | e.should.be.an.instanceOf(InvalidArgumentError); 24 | e.message.should.equal('Missing parameter: `headers`'); 25 | } 26 | }); 27 | 28 | it('should throw an error if `method` is missing', function() { 29 | try { 30 | new Request({ body: {}, headers: {} }); 31 | 32 | should.fail(); 33 | } catch (e) { 34 | e.should.be.an.instanceOf(InvalidArgumentError); 35 | e.message.should.equal('Missing parameter: `method`'); 36 | } 37 | }); 38 | 39 | it('should throw an error if `query` is missing', function() { 40 | try { 41 | new Request({ body: {}, headers: {}, method: {} }); 42 | 43 | should.fail(); 44 | } catch (e) { 45 | e.should.be.an.instanceOf(InvalidArgumentError); 46 | e.message.should.equal('Missing parameter: `query`'); 47 | } 48 | }); 49 | 50 | it('should set the `body`', function() { 51 | var request = new Request({ body: 'foo', headers: {}, method: {}, query: {} }); 52 | 53 | request.body.should.equal('foo'); 54 | }); 55 | 56 | it('should set the `headers`', function() { 57 | var request = new Request({ body: {}, headers: { foo: 'bar', QuX: 'biz' }, method: {}, query: {} }); 58 | 59 | request.headers.should.eql({ foo: 'bar', qux: 'biz' }); 60 | }); 61 | 62 | it('should set the `method`', function() { 63 | var request = new Request({ body: {}, headers: {}, method: 'biz', query: {} }); 64 | 65 | request.method.should.equal('biz'); 66 | }); 67 | 68 | it('should set the `query`', function() { 69 | var request = new Request({ body: {}, headers: {}, method: {}, query: 'baz' }); 70 | 71 | request.query.should.equal('baz'); 72 | }); 73 | }); 74 | 75 | describe('get()', function() { 76 | it('should return `undefined` if the field does not exist', function() { 77 | var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); 78 | 79 | (undefined === request.get('content-type')).should.be.true; 80 | }); 81 | 82 | it('should return the value if the field exists', function() { 83 | var request = new Request({ 84 | body: {}, 85 | headers: { 86 | 'content-type': 'text/html; charset=utf-8' 87 | }, 88 | method: {}, 89 | query: {} 90 | }); 91 | 92 | request.get('Content-Type').should.equal('text/html; charset=utf-8'); 93 | }); 94 | }); 95 | 96 | describe('is()', function() { 97 | it('should accept an array of `types`', function() { 98 | var request = new Request({ 99 | body: {}, 100 | headers: { 101 | 'content-type': 'application/json', 102 | 'transfer-encoding': 'chunked' 103 | }, 104 | method: {}, 105 | query: {} 106 | }); 107 | 108 | request.is(['html', 'json']).should.equal('json'); 109 | }); 110 | 111 | it('should accept multiple `types` as arguments', function() { 112 | var request = new Request({ 113 | body: {}, 114 | headers: { 115 | 'content-type': 'application/json', 116 | 'transfer-encoding': 'chunked' 117 | }, 118 | method: {}, 119 | query: {} 120 | }); 121 | 122 | request.is('html', 'json').should.equal('json'); 123 | }); 124 | 125 | it('should return the first matching type', function() { 126 | var request = new Request({ 127 | body: {}, 128 | headers: { 129 | 'content-type': 'text/html; charset=utf-8', 130 | 'transfer-encoding': 'chunked' 131 | }, 132 | method: {}, 133 | query: {} 134 | }); 135 | 136 | request.is('html').should.equal('html'); 137 | }); 138 | 139 | it('should return `false` if none of the `types` match', function() { 140 | var request = new Request({ 141 | body: {}, 142 | headers: { 143 | 'content-type': 'text/html; charset=utf-8', 144 | 'transfer-encoding': 'chunked' 145 | }, 146 | method: {}, 147 | query: {} 148 | }); 149 | 150 | request.is('json').should.be.false; 151 | }); 152 | 153 | it('should return `false` if the request has no body', function() { 154 | var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); 155 | 156 | request.is('text/html').should.be.false; 157 | }); 158 | }); 159 | }); 160 | -------------------------------------------------------------------------------- /test/integration/response-types/code-response-type_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var CodeResponseType = require('../../../lib/response-types/code-response-type'); 8 | var InvalidArgumentError = require('../../../lib/errors/invalid-argument-error'); 9 | var should = require('should'); 10 | var url = require('url'); 11 | 12 | /** 13 | * Test `CodeResponseType` integration. 14 | */ 15 | 16 | describe('CodeResponseType integration', function() { 17 | describe('constructor()', function() { 18 | it('should throw an error if `code` is missing', function() { 19 | try { 20 | new CodeResponseType(); 21 | 22 | should.fail(); 23 | } catch (e) { 24 | e.should.be.an.instanceOf(InvalidArgumentError); 25 | e.message.should.equal('Missing parameter: `code`'); 26 | } 27 | }); 28 | 29 | it('should set the `code`', function() { 30 | var responseType = new CodeResponseType('foo'); 31 | 32 | responseType.code.should.equal('foo'); 33 | }); 34 | }); 35 | 36 | describe('buildRedirectUri()', function() { 37 | it('should throw an error if the `redirectUri` is missing', function() { 38 | var responseType = new CodeResponseType('foo'); 39 | 40 | try { 41 | responseType.buildRedirectUri(); 42 | 43 | should.fail(); 44 | } catch (e) { 45 | e.should.be.an.instanceOf(InvalidArgumentError); 46 | e.message.should.equal('Missing parameter: `redirectUri`'); 47 | } 48 | }); 49 | 50 | it('should return the new redirect uri and set the `code` and `state` in the query', function() { 51 | var responseType = new CodeResponseType('foo'); 52 | var redirectUri = responseType.buildRedirectUri('http://example.com/cb'); 53 | 54 | url.format(redirectUri).should.equal('http://example.com/cb?code=foo'); 55 | }); 56 | 57 | it('should return the new redirect uri and append the `code` and `state` in the query', function() { 58 | var responseType = new CodeResponseType('foo'); 59 | var redirectUri = responseType.buildRedirectUri('http://example.com/cb?foo=bar'); 60 | 61 | url.format(redirectUri).should.equal('http://example.com/cb?foo=bar&code=foo'); 62 | }); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /test/integration/response_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var Response = require('../../lib/response'); 8 | 9 | /** 10 | * Test `Response` integration. 11 | */ 12 | 13 | describe('Response integration', function() { 14 | describe('constructor()', function() { 15 | it('should set the `body`', function() { 16 | var response = new Response({ body: 'foo', headers: {} }); 17 | 18 | response.body.should.equal('foo'); 19 | }); 20 | 21 | it('should set the `headers`', function() { 22 | var response = new Response({ body: {}, headers: { foo: 'bar', QuX: 'biz' } }); 23 | 24 | response.headers.should.eql({ foo: 'bar', qux: 'biz' }); 25 | }); 26 | 27 | it('should set the `status` to 200', function() { 28 | var response = new Response({ body: {}, headers: {} }); 29 | 30 | response.status.should.equal(200); 31 | }); 32 | }); 33 | 34 | describe('get()', function() { 35 | it('should return `undefined` if the field does not exist', function() { 36 | var response = new Response({ body: {}, headers: {} }); 37 | 38 | (undefined === response.get('content-type')).should.be.true; 39 | }); 40 | 41 | it('should return the value if the field exists', function() { 42 | var response = new Response({ body: {}, headers: { 'content-type': 'text/html; charset=utf-8' } }); 43 | 44 | response.get('Content-Type').should.equal('text/html; charset=utf-8'); 45 | }); 46 | }); 47 | 48 | describe('redirect()', function() { 49 | it('should set the location header to `url`', function() { 50 | var response = new Response({ body: {}, headers: {} }); 51 | 52 | response.redirect('http://example.com'); 53 | 54 | response.get('Location').should.equal('http://example.com'); 55 | }); 56 | 57 | it('should set the `status` to 302', function() { 58 | var response = new Response({ body: {}, headers: {} }); 59 | 60 | response.redirect('http://example.com'); 61 | 62 | response.status.should.equal(302); 63 | }); 64 | }); 65 | 66 | describe('set()', function() { 67 | it('should set the `field`', function() { 68 | var response = new Response({ body: {}, headers: {} }); 69 | 70 | response.set('foo', 'bar'); 71 | 72 | response.headers.should.eql({ foo: 'bar' }); 73 | }); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /test/integration/token-types/bearer-token-type_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var BearerTokenType = require('../../../lib/token-types/bearer-token-type'); 8 | var InvalidArgumentError = require('../../../lib/errors/invalid-argument-error'); 9 | var should = require('should'); 10 | 11 | /** 12 | * Test `BearerTokenType` integration. 13 | */ 14 | 15 | describe('BearerTokenType integration', function() { 16 | describe('constructor()', function() { 17 | it('should throw an error if `accessToken` is missing', function() { 18 | try { 19 | new BearerTokenType(); 20 | 21 | should.fail(); 22 | } catch (e) { 23 | e.should.be.an.instanceOf(InvalidArgumentError); 24 | e.message.should.equal('Missing parameter: `accessToken`'); 25 | } 26 | }); 27 | 28 | it('should set the `accessToken`', function() { 29 | var responseType = new BearerTokenType('foo', 'bar'); 30 | 31 | responseType.accessToken.should.equal('foo'); 32 | }); 33 | 34 | it('should set the `accessTokenLifetime`', function() { 35 | var responseType = new BearerTokenType('foo', 'bar'); 36 | 37 | responseType.accessTokenLifetime.should.equal('bar'); 38 | }); 39 | 40 | it('should set the `refreshToken`', function() { 41 | var responseType = new BearerTokenType('foo', 'bar', 'biz'); 42 | 43 | responseType.refreshToken.should.equal('biz'); 44 | }); 45 | }); 46 | 47 | describe('valueOf()', function() { 48 | it('should return the value representation', function() { 49 | var responseType = new BearerTokenType('foo', 'bar'); 50 | var value = responseType.valueOf(); 51 | 52 | value.should.eql({ 53 | access_token: 'foo', 54 | expires_in: 'bar', 55 | token_type: 'Bearer' 56 | }); 57 | }); 58 | 59 | it('should not include the `expires_in` if not given', function() { 60 | var responseType = new BearerTokenType('foo'); 61 | var value = responseType.valueOf(); 62 | 63 | value.should.eql({ 64 | access_token: 'foo', 65 | token_type: 'Bearer' 66 | }); 67 | }); 68 | 69 | it('should set `refresh_token` if `refreshToken` is defined', function() { 70 | var responseType = new BearerTokenType('foo', 'bar', 'biz'); 71 | var value = responseType.valueOf(); 72 | 73 | value.should.eql({ 74 | access_token: 'foo', 75 | expires_in: 'bar', 76 | refresh_token: 'biz', 77 | token_type: 'Bearer' 78 | }); 79 | }); 80 | 81 | it('should set `expires_in` if `accessTokenLifetime` is defined', function() { 82 | var responseType = new BearerTokenType('foo', 'bar', 'biz'); 83 | var value = responseType.valueOf(); 84 | 85 | value.should.eql({ 86 | access_token: 'foo', 87 | expires_in: 'bar', 88 | refresh_token: 'biz', 89 | token_type: 'Bearer' 90 | }); 91 | }); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /test/integration/utils/token-util_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var TokenUtil = require('../../../lib/utils/token-util'); 8 | var should = require('should'); 9 | 10 | /** 11 | * Test `TokenUtil` integration. 12 | */ 13 | 14 | describe('TokenUtil integration', function() { 15 | describe('generateRandomToken()', function() { 16 | it('should return a sha-1 token', function() { 17 | return TokenUtil.generateRandomToken() 18 | .then(function(token) { 19 | token.should.be.a.sha1; 20 | }) 21 | .catch(should.fail); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --require should 2 | --require test/assertions 3 | --ui bdd 4 | --reporter spec 5 | -------------------------------------------------------------------------------- /test/unit/grant-types/abstract-grant-type_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var AbstractGrantType = require('../../../lib/grant-types/abstract-grant-type'); 8 | var sinon = require('sinon'); 9 | var should = require('should'); 10 | 11 | /** 12 | * Test `AbstractGrantType`. 13 | */ 14 | 15 | describe('AbstractGrantType', function() { 16 | describe('generateAccessToken()', function() { 17 | it('should call `model.generateAccessToken()`', function() { 18 | var model = { 19 | generateAccessToken: sinon.stub().returns({ client: {}, expiresAt: new Date(), user: {} }) 20 | }; 21 | var handler = new AbstractGrantType({ accessTokenLifetime: 120, model: model }); 22 | 23 | return handler.generateAccessToken() 24 | .then(function() { 25 | model.generateAccessToken.callCount.should.equal(1); 26 | model.generateAccessToken.firstCall.thisValue.should.equal(model); 27 | }) 28 | .catch(should.fail); 29 | }); 30 | }); 31 | 32 | describe('generateRefreshToken()', function() { 33 | it('should call `model.generateRefreshToken()`', function() { 34 | var model = { 35 | generateRefreshToken: sinon.stub().returns({ client: {}, expiresAt: new Date(new Date() / 2), user: {} }) 36 | }; 37 | var handler = new AbstractGrantType({ accessTokenLifetime: 120, model: model }); 38 | 39 | return handler.generateRefreshToken() 40 | .then(function() { 41 | model.generateRefreshToken.callCount.should.equal(1); 42 | model.generateRefreshToken.firstCall.thisValue.should.equal(model); 43 | }) 44 | .catch(should.fail); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test/unit/grant-types/authorization-code-grant-type_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var AuthorizationCodeGrantType = require('../../../lib/grant-types/authorization-code-grant-type'); 8 | var Promise = require('bluebird'); 9 | var Request = require('../../../lib/request'); 10 | var sinon = require('sinon'); 11 | var should = require('should'); 12 | 13 | /** 14 | * Test `AuthorizationCodeGrantType`. 15 | */ 16 | 17 | describe('AuthorizationCodeGrantType', function() { 18 | describe('getAuthorizationCode()', function() { 19 | it('should call `model.getAuthorizationCode()`', function() { 20 | var model = { 21 | getAuthorizationCode: sinon.stub().returns({ authorizationCode: 12345, client: {}, expiresAt: new Date(new Date() * 2), user: {} }), 22 | revokeAuthorizationCode: function() {}, 23 | saveToken: function() {} 24 | }; 25 | var handler = new AuthorizationCodeGrantType({ accessTokenLifetime: 120, model: model }); 26 | var request = new Request({ body: { code: 12345 }, headers: {}, method: {}, query: {} }); 27 | var client = {}; 28 | 29 | return handler.getAuthorizationCode(request, client) 30 | .then(function() { 31 | model.getAuthorizationCode.callCount.should.equal(1); 32 | model.getAuthorizationCode.firstCall.args.should.have.length(1); 33 | model.getAuthorizationCode.firstCall.args[0].should.equal(12345); 34 | model.getAuthorizationCode.firstCall.thisValue.should.equal(model); 35 | }) 36 | .catch(should.fail); 37 | }); 38 | }); 39 | 40 | describe('revokeAuthorizationCode()', function() { 41 | it('should call `model.revokeAuthorizationCode()`', function() { 42 | var model = { 43 | getAuthorizationCode: function() {}, 44 | revokeAuthorizationCode: sinon.stub().returns(true), 45 | saveToken: function() {} 46 | }; 47 | var handler = new AuthorizationCodeGrantType({ accessTokenLifetime: 120, model: model }); 48 | var authorizationCode = {}; 49 | 50 | return handler.revokeAuthorizationCode(authorizationCode) 51 | .then(function() { 52 | model.revokeAuthorizationCode.callCount.should.equal(1); 53 | model.revokeAuthorizationCode.firstCall.args.should.have.length(1); 54 | model.revokeAuthorizationCode.firstCall.args[0].should.equal(authorizationCode); 55 | model.revokeAuthorizationCode.firstCall.thisValue.should.equal(model); 56 | }) 57 | .catch(should.fail); 58 | }); 59 | }); 60 | 61 | describe('saveToken()', function() { 62 | it('should call `model.saveToken()`', function() { 63 | var client = {}; 64 | var user = {}; 65 | var model = { 66 | getAuthorizationCode: function() {}, 67 | revokeAuthorizationCode: function() {}, 68 | saveToken: sinon.stub().returns(true) 69 | }; 70 | var handler = new AuthorizationCodeGrantType({ accessTokenLifetime: 120, model: model }); 71 | 72 | sinon.stub(handler, 'validateScope').returns('foobiz'); 73 | sinon.stub(handler, 'generateAccessToken').returns(Promise.resolve('foo')); 74 | sinon.stub(handler, 'generateRefreshToken').returns(Promise.resolve('bar')); 75 | sinon.stub(handler, 'getAccessTokenExpiresAt').returns(Promise.resolve('biz')); 76 | sinon.stub(handler, 'getRefreshTokenExpiresAt').returns(Promise.resolve('baz')); 77 | 78 | return handler.saveToken(user, client, 'foobar', 'foobiz') 79 | .then(function() { 80 | model.saveToken.callCount.should.equal(1); 81 | model.saveToken.firstCall.args.should.have.length(3); 82 | model.saveToken.firstCall.args[0].should.eql({ accessToken: 'foo', authorizationCode: 'foobar', accessTokenExpiresAt: 'biz', refreshToken: 'bar', refreshTokenExpiresAt: 'baz', scope: 'foobiz' }); 83 | model.saveToken.firstCall.args[1].should.equal(client); 84 | model.saveToken.firstCall.args[2].should.equal(user); 85 | model.saveToken.firstCall.thisValue.should.equal(model); 86 | }) 87 | .catch(should.fail); 88 | }); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /test/unit/grant-types/client-credentials-grant-type_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var ClientCredentialsGrantType = require('../../../lib/grant-types/client-credentials-grant-type'); 8 | var sinon = require('sinon'); 9 | var should = require('should'); 10 | 11 | /** 12 | * Test `ClientCredentialsGrantType`. 13 | */ 14 | 15 | describe('ClientCredentialsGrantType', function() { 16 | describe('getUserFromClient()', function() { 17 | it('should call `model.getUserFromClient()`', function() { 18 | var model = { 19 | getUserFromClient: sinon.stub().returns(true), 20 | saveToken: function() {} 21 | }; 22 | var handler = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); 23 | var client = {}; 24 | 25 | return handler.getUserFromClient(client) 26 | .then(function() { 27 | model.getUserFromClient.callCount.should.equal(1); 28 | model.getUserFromClient.firstCall.args.should.have.length(1); 29 | model.getUserFromClient.firstCall.args[0].should.equal(client); 30 | model.getUserFromClient.firstCall.thisValue.should.equal(model); 31 | }) 32 | .catch(should.fail); 33 | }); 34 | }); 35 | 36 | describe('saveToken()', function() { 37 | it('should call `model.saveToken()`', function() { 38 | var client = {}; 39 | var user = {}; 40 | var model = { 41 | getUserFromClient: function() {}, 42 | saveToken: sinon.stub().returns(true) 43 | }; 44 | var handler = new ClientCredentialsGrantType({ accessTokenLifetime: 120, model: model }); 45 | 46 | sinon.stub(handler, 'validateScope').returns('foobar'); 47 | sinon.stub(handler, 'generateAccessToken').returns('foo'); 48 | sinon.stub(handler, 'getAccessTokenExpiresAt').returns('biz'); 49 | 50 | return handler.saveToken(user, client, 'foobar') 51 | .then(function() { 52 | model.saveToken.callCount.should.equal(1); 53 | model.saveToken.firstCall.args.should.have.length(3); 54 | model.saveToken.firstCall.args[0].should.eql({ accessToken: 'foo', accessTokenExpiresAt: 'biz', scope: 'foobar' }); 55 | model.saveToken.firstCall.args[1].should.equal(client); 56 | model.saveToken.firstCall.args[2].should.equal(user); 57 | model.saveToken.firstCall.thisValue.should.equal(model); 58 | }) 59 | .catch(should.fail); 60 | }); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /test/unit/grant-types/password-grant-type_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var PasswordGrantType = require('../../../lib/grant-types/password-grant-type'); 8 | var Request = require('../../../lib/request'); 9 | var sinon = require('sinon'); 10 | var should = require('should'); 11 | 12 | /** 13 | * Test `PasswordGrantType`. 14 | */ 15 | 16 | describe('PasswordGrantType', function() { 17 | describe('getUser()', function() { 18 | it('should call `model.getUser()`', function() { 19 | var model = { 20 | getUser: sinon.stub().returns(true), 21 | saveToken: function() {} 22 | }; 23 | var handler = new PasswordGrantType({ accessTokenLifetime: 120, model: model }); 24 | var request = new Request({ body: { username: 'foo', password: 'bar' }, headers: {}, method: {}, query: {} }); 25 | 26 | return handler.getUser(request) 27 | .then(function() { 28 | model.getUser.callCount.should.equal(1); 29 | model.getUser.firstCall.args.should.have.length(2); 30 | model.getUser.firstCall.args[0].should.equal('foo'); 31 | model.getUser.firstCall.args[1].should.equal('bar'); 32 | model.getUser.firstCall.thisValue.should.equal(model); 33 | }) 34 | .catch(should.fail); 35 | }); 36 | }); 37 | 38 | describe('saveToken()', function() { 39 | it('should call `model.saveToken()`', function() { 40 | var client = {}; 41 | var user = {}; 42 | var model = { 43 | getUser: function() {}, 44 | saveToken: sinon.stub().returns(true) 45 | }; 46 | var handler = new PasswordGrantType({ accessTokenLifetime: 120, model: model }); 47 | 48 | sinon.stub(handler, 'validateScope').returns('foobar'); 49 | sinon.stub(handler, 'generateAccessToken').returns('foo'); 50 | sinon.stub(handler, 'generateRefreshToken').returns('bar'); 51 | sinon.stub(handler, 'getAccessTokenExpiresAt').returns('biz'); 52 | sinon.stub(handler, 'getRefreshTokenExpiresAt').returns('baz'); 53 | 54 | return handler.saveToken(user, client, 'foobar') 55 | .then(function() { 56 | model.saveToken.callCount.should.equal(1); 57 | model.saveToken.firstCall.args.should.have.length(3); 58 | model.saveToken.firstCall.args[0].should.eql({ accessToken: 'foo', accessTokenExpiresAt: 'biz', refreshToken: 'bar', refreshTokenExpiresAt: 'baz', scope: 'foobar' }); 59 | model.saveToken.firstCall.args[1].should.equal(client); 60 | model.saveToken.firstCall.args[2].should.equal(user); 61 | model.saveToken.firstCall.thisValue.should.equal(model); 62 | }) 63 | .catch(should.fail); 64 | }); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /test/unit/handlers/authenticate-handler_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var AuthenticateHandler = require('../../../lib/handlers/authenticate-handler'); 8 | var Request = require('../../../lib/request'); 9 | var sinon = require('sinon'); 10 | var should = require('should'); 11 | var ServerError = require('../../../lib/errors/server-error'); 12 | 13 | /** 14 | * Test `AuthenticateHandler`. 15 | */ 16 | 17 | describe('AuthenticateHandler', function() { 18 | describe('getTokenFromRequest()', function() { 19 | describe('with bearer token in the request authorization header', function() { 20 | it('should call `getTokenFromRequestHeader()`', function() { 21 | var handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); 22 | var request = new Request({ 23 | body: {}, 24 | headers: { 'Authorization': 'Bearer foo' }, 25 | method: {}, 26 | query: {} 27 | }); 28 | 29 | sinon.stub(handler, 'getTokenFromRequestHeader'); 30 | 31 | handler.getTokenFromRequest(request); 32 | 33 | handler.getTokenFromRequestHeader.callCount.should.equal(1); 34 | handler.getTokenFromRequestHeader.firstCall.args[0].should.equal(request); 35 | handler.getTokenFromRequestHeader.restore(); 36 | }); 37 | }); 38 | 39 | describe('with bearer token in the request query', function() { 40 | it('should call `getTokenFromRequestQuery()`', function() { 41 | var handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); 42 | var request = new Request({ 43 | body: {}, 44 | headers: {}, 45 | method: {}, 46 | query: { access_token: 'foo' } 47 | }); 48 | 49 | sinon.stub(handler, 'getTokenFromRequestQuery'); 50 | 51 | handler.getTokenFromRequest(request); 52 | 53 | handler.getTokenFromRequestQuery.callCount.should.equal(1); 54 | handler.getTokenFromRequestQuery.firstCall.args[0].should.equal(request); 55 | handler.getTokenFromRequestQuery.restore(); 56 | }); 57 | }); 58 | 59 | describe('with bearer token in the request body', function() { 60 | it('should call `getTokenFromRequestBody()`', function() { 61 | var handler = new AuthenticateHandler({ model: { getAccessToken: function() {} } }); 62 | var request = new Request({ 63 | body: { access_token: 'foo' }, 64 | headers: {}, 65 | method: {}, 66 | query: {} 67 | }); 68 | 69 | sinon.stub(handler, 'getTokenFromRequestBody'); 70 | 71 | handler.getTokenFromRequest(request); 72 | 73 | handler.getTokenFromRequestBody.callCount.should.equal(1); 74 | handler.getTokenFromRequestBody.firstCall.args[0].should.equal(request); 75 | handler.getTokenFromRequestBody.restore(); 76 | }); 77 | }); 78 | }); 79 | 80 | describe('getAccessToken()', function() { 81 | it('should call `model.getAccessToken()`', function() { 82 | var model = { 83 | getAccessToken: sinon.stub().returns({ user: {} }) 84 | }; 85 | var handler = new AuthenticateHandler({ model: model }); 86 | 87 | return handler.getAccessToken('foo') 88 | .then(function() { 89 | model.getAccessToken.callCount.should.equal(1); 90 | model.getAccessToken.firstCall.args.should.have.length(1); 91 | model.getAccessToken.firstCall.args[0].should.equal('foo'); 92 | model.getAccessToken.firstCall.thisValue.should.equal(model); 93 | }) 94 | .catch(should.fail); 95 | }); 96 | }); 97 | 98 | describe('validateAccessToken()', function() { 99 | it('should fail if token has no valid `accessTokenExpiresAt` date', function() { 100 | var model = { 101 | getAccessToken: function() {} 102 | }; 103 | var handler = new AuthenticateHandler({ model: model }); 104 | 105 | var failed = false; 106 | try { 107 | handler.validateAccessToken({ 108 | user: {} 109 | }); 110 | } 111 | catch (err) { 112 | err.should.be.an.instanceOf(ServerError); 113 | failed = true; 114 | } 115 | failed.should.equal(true); 116 | }); 117 | 118 | it('should succeed if token has valid `accessTokenExpiresAt` date', function() { 119 | var model = { 120 | getAccessToken: function() {} 121 | }; 122 | var handler = new AuthenticateHandler({ model: model }); 123 | try { 124 | handler.validateAccessToken({ 125 | user: {}, 126 | accessTokenExpiresAt: new Date(new Date().getTime() + 10000) 127 | }); 128 | } 129 | catch (err) { 130 | should.fail(); 131 | } 132 | }); 133 | }); 134 | 135 | describe('verifyScope()', function() { 136 | it('should call `model.getAccessToken()` if scope is defined', function() { 137 | var model = { 138 | getAccessToken: function() {}, 139 | verifyScope: sinon.stub().returns(true) 140 | }; 141 | var handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: true, model: model, scope: 'bar' }); 142 | 143 | return handler.verifyScope('foo') 144 | .then(function() { 145 | model.verifyScope.callCount.should.equal(1); 146 | model.verifyScope.firstCall.args.should.have.length(2); 147 | model.verifyScope.firstCall.args[0].should.equal('foo', 'bar'); 148 | model.verifyScope.firstCall.thisValue.should.equal(model); 149 | }) 150 | .catch(should.fail); 151 | }); 152 | }); 153 | }); 154 | -------------------------------------------------------------------------------- /test/unit/handlers/authorize-handler_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var AuthorizeHandler = require('../../../lib/handlers/authorize-handler'); 8 | var Request = require('../../../lib/request'); 9 | var Response = require('../../../lib/response'); 10 | var Promise = require('bluebird'); 11 | var sinon = require('sinon'); 12 | var should = require('should'); 13 | 14 | /** 15 | * Test `AuthorizeHandler`. 16 | */ 17 | 18 | describe('AuthorizeHandler', function() { 19 | describe('generateAuthorizationCode()', function() { 20 | it('should call `model.generateAuthorizationCode()`', function() { 21 | var model = { 22 | generateAuthorizationCode: sinon.stub().returns({}), 23 | getAccessToken: function() {}, 24 | getClient: function() {}, 25 | saveAuthorizationCode: function() {} 26 | }; 27 | var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); 28 | 29 | return handler.generateAuthorizationCode() 30 | .then(function() { 31 | model.generateAuthorizationCode.callCount.should.equal(1); 32 | model.generateAuthorizationCode.firstCall.thisValue.should.equal(model); 33 | }) 34 | .catch(should.fail); 35 | }); 36 | }); 37 | 38 | describe('getClient()', function() { 39 | it('should call `model.getClient()`', function() { 40 | var model = { 41 | getAccessToken: function() {}, 42 | getClient: sinon.stub().returns({ grants: ['authorization_code'], redirectUris: ['http://example.com/cb'] }), 43 | saveAuthorizationCode: function() {} 44 | }; 45 | var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); 46 | var request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, method: {}, query: {} }); 47 | 48 | return handler.getClient(request) 49 | .then(function() { 50 | model.getClient.callCount.should.equal(1); 51 | model.getClient.firstCall.args.should.have.length(2); 52 | model.getClient.firstCall.args[0].should.equal(12345); 53 | model.getClient.firstCall.thisValue.should.equal(model); 54 | }) 55 | .catch(should.fail); 56 | }); 57 | }); 58 | 59 | describe('getUser()', function() { 60 | it('should call `authenticateHandler.getUser()`', function() { 61 | var authenticateHandler = { handle: sinon.stub().returns(Promise.resolve({})) }; 62 | var model = { 63 | getClient: function() {}, 64 | saveAuthorizationCode: function() {} 65 | }; 66 | var handler = new AuthorizeHandler({ authenticateHandler: authenticateHandler, authorizationCodeLifetime: 120, model: model }); 67 | var request = new Request({ body: {}, headers: {}, method: {}, query: {} }); 68 | var response = new Response(); 69 | 70 | return handler.getUser(request, response) 71 | .then(function() { 72 | authenticateHandler.handle.callCount.should.equal(1); 73 | authenticateHandler.handle.firstCall.args.should.have.length(2); 74 | authenticateHandler.handle.firstCall.args[0].should.equal(request); 75 | authenticateHandler.handle.firstCall.args[1].should.equal(response); 76 | }) 77 | .catch(should.fail); 78 | }); 79 | }); 80 | 81 | describe('saveAuthorizationCode()', function() { 82 | it('should call `model.saveAuthorizationCode()`', function() { 83 | var model = { 84 | getAccessToken: function() {}, 85 | getClient: function() {}, 86 | saveAuthorizationCode: sinon.stub().returns({}) 87 | }; 88 | var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); 89 | 90 | return handler.saveAuthorizationCode('foo', 'bar', 'qux', 'biz', 'baz', 'boz') 91 | .then(function() { 92 | model.saveAuthorizationCode.callCount.should.equal(1); 93 | model.saveAuthorizationCode.firstCall.args.should.have.length(3); 94 | model.saveAuthorizationCode.firstCall.args[0].should.eql({ authorizationCode: 'foo', expiresAt: 'bar', redirectUri: 'baz', scope: 'qux' }); 95 | model.saveAuthorizationCode.firstCall.args[1].should.equal('biz'); 96 | model.saveAuthorizationCode.firstCall.args[2].should.equal('boz'); 97 | model.saveAuthorizationCode.firstCall.thisValue.should.equal(model); 98 | }) 99 | .catch(should.fail); 100 | }); 101 | }); 102 | }); 103 | -------------------------------------------------------------------------------- /test/unit/handlers/token-handler_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var Request = require('../../../lib/request'); 8 | var TokenHandler = require('../../../lib/handlers/token-handler'); 9 | var sinon = require('sinon'); 10 | var should = require('should'); 11 | 12 | /** 13 | * Test `TokenHandler`. 14 | */ 15 | 16 | describe('TokenHandler', function() { 17 | describe('getClient()', function() { 18 | it('should call `model.getClient()`', function() { 19 | var model = { 20 | getClient: sinon.stub().returns({ grants: ['password'] }), 21 | saveToken: function() {} 22 | }; 23 | var handler = new TokenHandler({ accessTokenLifetime: 120, model: model, refreshTokenLifetime: 120 }); 24 | var request = new Request({ body: { client_id: 12345, client_secret: 'secret' }, headers: {}, method: {}, query: {} }); 25 | 26 | return handler.getClient(request) 27 | .then(function() { 28 | model.getClient.callCount.should.equal(1); 29 | model.getClient.firstCall.args.should.have.length(2); 30 | model.getClient.firstCall.args[0].should.equal(12345); 31 | model.getClient.firstCall.args[1].should.equal('secret'); 32 | model.getClient.firstCall.thisValue.should.equal(model); 33 | }) 34 | .catch(should.fail); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/unit/models/token-model_test.js: -------------------------------------------------------------------------------- 1 | var TokenModel = require('../../../lib/models/token-model'); 2 | 3 | /** 4 | * Test `Server`. 5 | */ 6 | 7 | describe('Model', function() { 8 | describe('constructor()', function() { 9 | it('should calculate `accessTokenLifetime` if `accessTokenExpiresAt` is set', function() { 10 | var atExpiresAt = new Date(); 11 | atExpiresAt.setHours(new Date().getHours() + 1); 12 | 13 | var data = { 14 | accessToken: 'foo', 15 | client: 'bar', 16 | user: 'tar', 17 | accessTokenExpiresAt: atExpiresAt 18 | }; 19 | 20 | var model = new TokenModel(data); 21 | model.accessTokenLifetime.should.be.Number; 22 | model.accessTokenLifetime.should.be.approximately(3600, 2); 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /test/unit/request_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var Request = require('../../lib/request'); 8 | var should = require('should'); 9 | 10 | /** 11 | * Test `Request`. 12 | */ 13 | 14 | function generateBaseRequest() { 15 | return { 16 | query: { 17 | foo: 'bar' 18 | }, 19 | method: 'GET', 20 | headers: { 21 | bar: 'foo' 22 | }, 23 | body: { 24 | foobar: 'barfoo' 25 | } 26 | }; 27 | } 28 | 29 | describe('Request', function() { 30 | it('should instantiate with a basic request', function() { 31 | var originalRequest = generateBaseRequest(); 32 | 33 | var request = new Request(originalRequest); 34 | request.headers.should.eql(originalRequest.headers); 35 | request.method.should.eql(originalRequest.method); 36 | request.query.should.eql(originalRequest.query); 37 | request.body.should.eql(originalRequest.body); 38 | }); 39 | 40 | it('should allow a request to be passed without a body', function() { 41 | var originalRequest = generateBaseRequest(); 42 | delete originalRequest.body; 43 | 44 | var request = new Request(originalRequest); 45 | request.headers.should.eql(originalRequest.headers); 46 | request.method.should.eql(originalRequest.method); 47 | request.query.should.eql(originalRequest.query); 48 | request.body.should.eql({}); 49 | }); 50 | 51 | it('should throw if headers are not passed to the constructor', function() { 52 | var originalRequest = generateBaseRequest(); 53 | delete originalRequest.headers; 54 | 55 | (function() { 56 | new Request(originalRequest); 57 | }).should.throw('Missing parameter: `headers`'); 58 | }); 59 | 60 | it('should throw if query string isn\'t passed to the constructor', function() { 61 | var originalRequest = generateBaseRequest(); 62 | delete originalRequest.query; 63 | 64 | (function() { 65 | new Request(originalRequest); 66 | }).should.throw('Missing parameter: `query`'); 67 | }); 68 | 69 | it('should throw if method isn\'t passed to the constructor', function() { 70 | var originalRequest = generateBaseRequest(); 71 | delete originalRequest.method; 72 | 73 | (function() { 74 | new Request(originalRequest); 75 | }).should.throw('Missing parameter: `method`'); 76 | }); 77 | 78 | it('should convert all header keys to lowercase', function() { 79 | var originalRequest = generateBaseRequest(); 80 | originalRequest.headers = { 81 | Foo: 'bar', 82 | BAR: 'foo' 83 | }; 84 | 85 | var request = new Request(originalRequest); 86 | request.headers.foo.should.eql('bar'); 87 | request.headers.bar.should.eql('foo'); 88 | should.not.exist(request.headers.Foo); 89 | should.not.exist(request.headers.BAR); 90 | }); 91 | 92 | it('should include additional properties passed in the request', function() { 93 | var originalRequest = generateBaseRequest(); 94 | originalRequest.custom = { 95 | newFoo: 'newBar' 96 | }; 97 | 98 | originalRequest.custom2 = { 99 | newBar: 'newFoo' 100 | }; 101 | 102 | var request = new Request(originalRequest); 103 | request.headers.should.eql(originalRequest.headers); 104 | request.method.should.eql(originalRequest.method); 105 | request.query.should.eql(originalRequest.query); 106 | request.body.should.eql(originalRequest.body); 107 | request.custom.should.eql(originalRequest.custom); 108 | request.custom2.should.eql(originalRequest.custom2); 109 | }); 110 | 111 | it('should include additional properties passed in the request', function() { 112 | var originalRequest = generateBaseRequest(); 113 | originalRequest.custom = { 114 | newFoo: 'newBar' 115 | }; 116 | 117 | originalRequest.custom2 = { 118 | newBar: 'newFoo' 119 | }; 120 | 121 | var request = new Request(originalRequest); 122 | request.headers.should.eql(originalRequest.headers); 123 | request.method.should.eql(originalRequest.method); 124 | request.query.should.eql(originalRequest.query); 125 | request.body.should.eql(originalRequest.body); 126 | request.custom.should.eql(originalRequest.custom); 127 | request.custom2.should.eql(originalRequest.custom2); 128 | }); 129 | 130 | it('should allow getting of headers using `request.get`', function() { 131 | var originalRequest = generateBaseRequest(); 132 | 133 | var request = new Request(originalRequest); 134 | request.get('bar').should.eql(originalRequest.headers.bar); 135 | }); 136 | 137 | it('should allow getting of headers using `request.get`', function() { 138 | var originalRequest = generateBaseRequest(); 139 | 140 | var request = new Request(originalRequest); 141 | request.get('bar').should.eql(originalRequest.headers.bar); 142 | }); 143 | 144 | it('should allow getting of headers using `request.get`', function() { 145 | var originalRequest = generateBaseRequest(); 146 | 147 | var request = new Request(originalRequest); 148 | request.get('bar').should.eql(originalRequest.headers.bar); 149 | }); 150 | 151 | it('should validate the content-type', function() { 152 | var originalRequest = generateBaseRequest(); 153 | originalRequest.headers['content-type'] = 'application/x-www-form-urlencoded'; 154 | originalRequest.headers['content-length'] = JSON.stringify(originalRequest.body).length; 155 | 156 | var request = new Request(originalRequest); 157 | request.is('application/x-www-form-urlencoded').should.eql('application/x-www-form-urlencoded'); 158 | }); 159 | 160 | it('should return false if the content-type is invalid', function() { 161 | var originalRequest = generateBaseRequest(); 162 | originalRequest.headers['content-type'] = 'application/x-www-form-urlencoded'; 163 | originalRequest.headers['content-length'] = JSON.stringify(originalRequest.body).length; 164 | 165 | var request = new Request(originalRequest); 166 | request.is('application/json').should.eql(false); 167 | }); 168 | }); 169 | -------------------------------------------------------------------------------- /test/unit/response_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var Response = require('../../lib/response'); 8 | var should = require('should'); 9 | 10 | /** 11 | * Test `Request`. 12 | */ 13 | 14 | function generateBaseResponse() { 15 | return { 16 | headers: { 17 | bar: 'foo' 18 | }, 19 | body: { 20 | foobar: 'barfoo' 21 | } 22 | }; 23 | } 24 | 25 | describe('Request', function() { 26 | it('should instantiate with a basic request', function() { 27 | var originalResponse = generateBaseResponse(); 28 | 29 | var response = new Response(originalResponse); 30 | response.headers.should.eql(originalResponse.headers); 31 | response.body.should.eql(originalResponse.body); 32 | response.status.should.eql(200); 33 | }); 34 | 35 | it('should allow a response to be passed without a body', function() { 36 | var originalResponse = generateBaseResponse(); 37 | delete originalResponse.body; 38 | 39 | var response = new Response(originalResponse); 40 | response.headers.should.eql(originalResponse.headers); 41 | response.body.should.eql({}); 42 | response.status.should.eql(200); 43 | }); 44 | 45 | it('should allow a response to be passed without headers', function() { 46 | var originalResponse = generateBaseResponse(); 47 | delete originalResponse.headers; 48 | 49 | var response = new Response(originalResponse); 50 | response.headers.should.eql({}); 51 | response.body.should.eql(originalResponse.body); 52 | response.status.should.eql(200); 53 | }); 54 | 55 | it('should convert all header keys to lowercase', function() { 56 | var originalResponse = generateBaseResponse(); 57 | originalResponse.headers = { 58 | Foo: 'bar', 59 | BAR: 'foo' 60 | }; 61 | 62 | var response = new Response(originalResponse); 63 | response.headers.foo.should.eql('bar'); 64 | response.headers.bar.should.eql('foo'); 65 | should.not.exist(response.headers.Foo); 66 | should.not.exist(response.headers.BAR); 67 | }); 68 | 69 | it('should include additional properties passed in the response', function() { 70 | var originalResponse = generateBaseResponse(); 71 | originalResponse.custom = { 72 | newFoo: 'newBar' 73 | }; 74 | 75 | originalResponse.custom2 = { 76 | newBar: 'newFoo' 77 | }; 78 | 79 | var response = new Response(originalResponse); 80 | response.headers.should.eql(originalResponse.headers); 81 | response.body.should.eql(originalResponse.body); 82 | response.custom.should.eql(originalResponse.custom); 83 | response.custom2.should.eql(originalResponse.custom2); 84 | }); 85 | 86 | it('should allow getting of headers using `response.get`', function() { 87 | var originalResponse = generateBaseResponse(); 88 | 89 | var response = new Response(originalResponse); 90 | response.get('bar').should.eql(originalResponse.headers.bar); 91 | }); 92 | 93 | it('should allow getting of headers using `response.get`', function() { 94 | var originalResponse = generateBaseResponse(); 95 | 96 | var response = new Response(originalResponse); 97 | response.get('bar').should.eql(originalResponse.headers.bar); 98 | }); 99 | 100 | it('should allow setting of headers using `response.set`', function() { 101 | var originalResponse = generateBaseResponse(); 102 | 103 | var response = new Response(originalResponse); 104 | response.headers.should.eql(originalResponse.headers); 105 | response.set('newheader', 'newvalue'); 106 | response.headers.bar.should.eql('foo'); 107 | response.headers.newheader.should.eql('newvalue'); 108 | }); 109 | 110 | it('should process redirect', function() { 111 | var originalResponse = generateBaseResponse(); 112 | 113 | var response = new Response(originalResponse); 114 | response.headers.should.eql(originalResponse.headers); 115 | response.status.should.eql(200); 116 | response.redirect('http://foo.bar'); 117 | response.headers.location.should.eql('http://foo.bar'); 118 | response.status.should.eql(302); 119 | }); 120 | }); 121 | -------------------------------------------------------------------------------- /test/unit/server_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var AuthenticateHandler = require('../../lib/handlers/authenticate-handler'); 8 | var AuthorizeHandler = require('../../lib/handlers/authorize-handler'); 9 | var Promise = require('bluebird'); 10 | var Server = require('../../lib/server'); 11 | var TokenHandler = require('../../lib/handlers/token-handler'); 12 | var sinon = require('sinon'); 13 | 14 | /** 15 | * Test `Server`. 16 | */ 17 | 18 | describe('Server', function() { 19 | describe('authenticate()', function() { 20 | it('should call `handle`', function() { 21 | var model = { 22 | getAccessToken: function() {} 23 | }; 24 | var server = new Server({ model: model }); 25 | 26 | sinon.stub(AuthenticateHandler.prototype, 'handle').returns(Promise.resolve()); 27 | 28 | server.authenticate('foo'); 29 | 30 | AuthenticateHandler.prototype.handle.callCount.should.equal(1); 31 | AuthenticateHandler.prototype.handle.firstCall.args[0].should.equal('foo'); 32 | AuthenticateHandler.prototype.handle.restore(); 33 | }); 34 | 35 | it('should map string passed as `options` to `options.scope`', function() { 36 | var model = { 37 | getAccessToken: function() {}, 38 | verifyScope: function() {} 39 | }; 40 | var server = new Server({ model: model }); 41 | 42 | sinon.stub(AuthenticateHandler.prototype, 'handle').returns(Promise.resolve()); 43 | 44 | server.authenticate('foo', 'bar', 'test'); 45 | 46 | AuthenticateHandler.prototype.handle.callCount.should.equal(1); 47 | AuthenticateHandler.prototype.handle.firstCall.args[0].should.equal('foo'); 48 | AuthenticateHandler.prototype.handle.firstCall.args[1].should.equal('bar'); 49 | AuthenticateHandler.prototype.handle.firstCall.thisValue.should.have.property('scope', 'test'); 50 | AuthenticateHandler.prototype.handle.restore(); 51 | }); 52 | }); 53 | 54 | describe('authorize()', function() { 55 | it('should call `handle`', function() { 56 | var model = { 57 | getAccessToken: function() {}, 58 | getClient: function() {}, 59 | saveAuthorizationCode: function() {} 60 | }; 61 | var server = new Server({ model: model }); 62 | 63 | sinon.stub(AuthorizeHandler.prototype, 'handle').returns(Promise.resolve()); 64 | 65 | server.authorize('foo', 'bar'); 66 | 67 | AuthorizeHandler.prototype.handle.callCount.should.equal(1); 68 | AuthorizeHandler.prototype.handle.firstCall.args[0].should.equal('foo'); 69 | AuthorizeHandler.prototype.handle.restore(); 70 | }); 71 | }); 72 | 73 | describe('token()', function() { 74 | it('should call `handle`', function() { 75 | var model = { 76 | getClient: function() {}, 77 | saveToken: function() {} 78 | }; 79 | var server = new Server({ model: model }); 80 | 81 | sinon.stub(TokenHandler.prototype, 'handle').returns(Promise.resolve()); 82 | 83 | server.token('foo', 'bar'); 84 | 85 | TokenHandler.prototype.handle.callCount.should.equal(1); 86 | TokenHandler.prototype.handle.firstCall.args[0].should.equal('foo'); 87 | TokenHandler.prototype.handle.restore(); 88 | }); 89 | }); 90 | }); 91 | --------------------------------------------------------------------------------