├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── node.yml ├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── SPONSORS.md ├── lib ├── errors │ ├── authorizationerror.js │ ├── badrequesterror.js │ ├── forbiddenerror.js │ ├── oauth2error.js │ └── tokenerror.js ├── exchange │ ├── authorizationCode.js │ ├── clientCredentials.js │ ├── password.js │ └── refreshToken.js ├── grant │ ├── code.js │ └── token.js ├── index.js ├── middleware │ ├── authorization.js │ ├── authorizationErrorHandler.js │ ├── decision.js │ ├── errorHandler.js │ ├── resume.js │ ├── token.js │ └── transactionLoader.js ├── response │ ├── fragment.js │ └── query.js ├── server.js ├── txn │ └── session.js ├── unorderedlist.js └── utils.js ├── package-lock.json ├── package.json └── test ├── bootstrap └── node.js ├── errors ├── authorizationerror.test.js ├── badrequesterror.test.js ├── forbiddenerror.test.js └── tokenerror.test.js ├── exchange ├── authorizationCode.test.js ├── clientCredentials.test.js ├── password.test.js └── refreshToken.test.js ├── grant ├── code.test.js └── token.test.js ├── middleware ├── authorization.immediate.test.js ├── authorization.test.js ├── authorizationErrorHandler.test.js ├── decision.test.js ├── errorHandler.test.js ├── resume.test.js ├── token.test.js └── transactionLoader.test.js ├── mock └── store.js ├── package.test.js ├── server.exchange.test.js ├── server.grant.test.js ├── server.request.test.js ├── server.response.error.test.js ├── server.response.test.js ├── server.serialization.test.js ├── server.test.js └── unorderedlist.test.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: jaredhanson 2 | patreon: jaredhanson 3 | ko_fi: jaredhanson 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ** READ THIS FIRST! ** 2 | 3 | #### Are you looking for help? 4 | 5 | Reminder: The issue tracker is not a support forum. 6 | 7 | Issues should only be filed in this project once they are able to be reproduced 8 | and confirmed as a flaw in the software or incorrect information in associated 9 | documentation. 10 | 11 | If you are encountering problems integrating this module into your application, 12 | please post a question on the [discussion forum](https://github.com/jaredhanson/oauth2orize-discuss) 13 | rather than filing an issue. 14 | 15 | #### Is this a security issue? 16 | 17 | Do not open issues that might have security implications. Potential security 18 | vulnerabilities should be reported privately to jaredhanson@gmail.com. Once any 19 | vulnerabilities have been repaired, the details will be disclosed publicly in a 20 | responsible manner. This also allows time for coordinating with affected parties 21 | in order to mitigate negative consequences. 22 | 23 | 24 | If neither of the above two scenarios apply to your situation, you should open 25 | an issue. Delete this paragraph and the text above, and fill in the information 26 | requested below. 27 | 28 | 29 | 30 | 31 | 32 | 33 | ### Expected behavior 34 | 35 | 36 | 37 | ### Actual behavior 38 | 39 | 40 | 41 | ### Steps to reproduce 42 | 43 | 44 | 45 | ```js 46 | // Format code using Markdown code blocks 47 | ``` 48 | 49 | ### Environment 50 | 51 | * Operating System: 52 | * Node version: 53 | * oauth2orize version: 54 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ** READ THIS FIRST! ** 2 | 3 | #### Are you implementing a new feature? 4 | 5 | Requests for new features should first be discussed on the [developer forum](https://github.com/jaredhanson/oauth2orize-develop). 6 | This allows the community to gather feedback and assess whether or not there is 7 | an existing way to achieve the desired functionality. 8 | 9 | If it is determined that a new feature needs to be implemented, include a link 10 | to the relevant discussion along with the pull request. 11 | 12 | #### Is this a security patch? 13 | 14 | Do not open pull requests that might have security implications. Potential 15 | security vulnerabilities should be reported privately to jaredhanson@gmail.com. 16 | Once any vulnerabilities have been repaired, the details will be disclosed 17 | publicly in a responsible manner. This also allows time for coordinating with 18 | affected parties in order to mitigate negative consequences. 19 | 20 | 21 | If neither of the above two scenarios apply to your situation, you should open 22 | a pull request. Delete this paragraph and the text above, and fill in the 23 | information requested below. 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | ### Checklist 33 | 34 | 35 | 36 | 37 | - [ ] I have read the [CONTRIBUTING](https://github.com/jaredhanson/oauth2orize/blob/master/CONTRIBUTING.md) guidelines. 38 | - [ ] I have added test cases which verify the correct operation of this feature or patch. 39 | - [ ] I have added documentation pertaining to this feature or patch. 40 | - [ ] The automated test suite (`$ make test`) executes successfully. 41 | - [ ] The automated code linting (`$ make lint`) executes successfully. 42 | -------------------------------------------------------------------------------- /.github/workflows/node.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: 20 | - '17' 21 | - '16' 22 | - '14' 23 | - '12' 24 | - '10' 25 | - '8' 26 | - '6' 27 | - '4' 28 | # - '3' # io.js 29 | # - '2' # io.js 30 | # - '1' # io.js 31 | - '0.12' 32 | - '0.10' 33 | # - '0.8' 34 | 35 | steps: 36 | - uses: actions/checkout@v2 37 | - name: Use Node.js ${{ matrix.node-version }} 38 | uses: actions/setup-node@v2 39 | with: 40 | node-version: ${{ matrix.node-version }} 41 | - run: npm install 42 | - run: npm test 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | docs/ 2 | reports/ 3 | 4 | # Mac OS X 5 | .DS_Store 6 | 7 | # Node.js 8 | node_modules 9 | npm-debug.log 10 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "bitwise": true, 4 | "camelcase": true, 5 | "curly": true, 6 | "forin": true, 7 | "immed": true, 8 | "latedef": true, 9 | "newcap": true, 10 | "noarg": true, 11 | "noempty": true, 12 | "nonew": true, 13 | "quotmark": "single", 14 | "undef": true, 15 | "unused": true, 16 | "trailing": true, 17 | "laxcomma": true 18 | } 19 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | CONTRIBUTING.md 2 | Makefile 3 | SPONSORS.md 4 | docs/ 5 | examples/ 6 | reports/ 7 | test/ 8 | 9 | .github/ 10 | .jshintrc 11 | .travis.yml 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: "node_js" 2 | node_js: 3 | - "8" 4 | - "7" 5 | - "6" 6 | - "5" 7 | - "4" 8 | - "3" # io.js 9 | - "2" # io.js 10 | - "1" # io.js 11 | - "0.12" 12 | - "0.10" 13 | - "0.8" 14 | # - "0.6" 15 | 16 | 17 | before_install: 18 | - "npm install make-node@0.3.x -g" 19 | - "preinstall-compat" 20 | 21 | script: 22 | - "make test-cov" 23 | 24 | after_success: 25 | - "make report-cov" 26 | 27 | sudo: false 28 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [1.12.0] - 2023-10-13 10 | ### Added 11 | - Optional `extend` callback to `grant.code` setup function, used to extend the 12 | authorization response. Needed to support extensions such as [OpenID Connect 13 | Session Management 1.0](https://openid.net/specs/openid-connect-session-1_0.html). 14 | 15 | ## [1.11.1] - 2021-11-17 16 | ### Fixed 17 | - Optional `complete` callback to `Server#_respond` defaulted to no-op, fixing 18 | "TypeError: complete is not a function" exceptions in cases where this function 19 | is being called from outside this package without the argument. 20 | 21 | ## [1.11.0] - 2017-11-02 22 | 23 | ## [1.10.0] - 2017-08-14 24 | 25 | [Unreleased]: https://github.com/jaredhanson/oauth2orize/compare/v1.12.0...HEAD 26 | [1.11.1]: https://github.com/jaredhanson/oauth2orize/compare/v1.11.1...v1.12.0 27 | [1.11.1]: https://github.com/jaredhanson/oauth2orize/compare/v1.11.0...v1.11.1 28 | [1.11.0]: https://github.com/jaredhanson/oauth2orize/compare/v1.10.0...v1.11.0 29 | [1.10.0]: https://github.com/jaredhanson/oauth2orize/compare/v1.9.0...v1.10.0 30 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | ### Tests 4 | 5 | The test suite is located in the `test/` directory. All new features are 6 | expected to have corresponding test cases with complete code coverage. Patches 7 | that increase test coverage are happily accepted. 8 | 9 | Ensure that the test suite passes by executing: 10 | 11 | ```bash 12 | $ make test 13 | ``` 14 | 15 | Coverage reports can be generated and viewed by executing: 16 | 17 | ```bash 18 | $ make test-cov 19 | $ make view-cov 20 | ``` 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2012-2021 Jared Hanson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include node_modules/make-node/main.mk 2 | 3 | 4 | SOURCES = lib/*.js lib/**/*.js 5 | TESTS = test/*.test.js test/**/*.test.js 6 | 7 | LCOVFILE = ./reports/coverage/lcov.info 8 | 9 | MOCHAFLAGS = --require ./test/bootstrap/node 10 | 11 | 12 | view-docs: 13 | open ./docs/index.html 14 | 15 | view-cov: 16 | open ./reports/coverage/lcov-report/index.html 17 | 18 | clean: clean-docs clean-cov 19 | -rm -r $(REPORTSDIR) 20 | 21 | clobber: clean 22 | -rm -r node_modules 23 | 24 | 25 | .PHONY: clean clobber 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OAuth2orize 2 | 3 | OAuth2orize is an authorization server toolkit for Node.js. It provides a suite 4 | of middleware that, combined with [Passport](http://passportjs.org/) 5 | authentication strategies and application-specific route handlers, can be used 6 | to assemble a server that implements the [OAuth 2.0](http://tools.ietf.org/html/rfc6749) 7 | protocol. 8 | 9 | --- 10 | 11 |

12 | Advertisement 13 |
14 | Node.js API Masterclass With Express & MongoDB
Create a real world backend for a bootcamp directory app 15 |

16 | 17 | --- 18 | 19 | Status: 20 | [![Build](https://img.shields.io/travis/jaredhanson/oauth2orize.svg)](https://travis-ci.org/jaredhanson/oauth2orize) 21 | [![Coverage](https://img.shields.io/coveralls/jaredhanson/oauth2orize.svg)](https://coveralls.io/r/jaredhanson/oauth2orize) 22 | [![Dependencies](https://img.shields.io/david/jaredhanson/oauth2orize.svg)](https://david-dm.org/jaredhanson/oauth2orize) 23 | 24 | 25 | ## Install 26 | 27 | $ npm install oauth2orize 28 | 29 | ## Usage 30 | 31 | OAuth 2.0 defines an authorization framework, allowing an extensible set of 32 | authorization grants to be exchanged for access tokens. Implementations are 33 | free to choose what grant types to support, by using bundled middleware to 34 | support common types or plugins to support extension types. 35 | 36 | #### Create an OAuth Server 37 | 38 | Call `createServer()` to create a new OAuth 2.0 server. This instance exposes 39 | middleware that will be mounted in routes, as well as configuration options. 40 | 41 | ```javascript 42 | var server = oauth2orize.createServer(); 43 | ``` 44 | 45 | #### Register Grants 46 | 47 | A client must obtain permission from a user before it is issued an access token. 48 | This permission is known as a grant, the most common type of which is an 49 | authorization code. 50 | ```javascript 51 | server.grant(oauth2orize.grant.code(function(client, redirectURI, user, ares, done) { 52 | var code = utils.uid(16); 53 | 54 | var ac = new AuthorizationCode(code, client.id, redirectURI, user.id, ares.scope); 55 | ac.save(function(err) { 56 | if (err) { return done(err); } 57 | return done(null, code); 58 | }); 59 | })); 60 | ``` 61 | 62 | OAuth2orize also bundles support for implicit token grants. 63 | 64 | #### Register Exchanges 65 | 66 | After a client has obtained an authorization grant from the user, that grant can 67 | be exchanged for an access token. 68 | 69 | ```javascript 70 | server.exchange(oauth2orize.exchange.code(function(client, code, redirectURI, done) { 71 | AuthorizationCode.findOne(code, function(err, code) { 72 | if (err) { return done(err); } 73 | if (client.id !== code.clientId) { return done(null, false); } 74 | if (redirectURI !== code.redirectUri) { return done(null, false); } 75 | 76 | var token = utils.uid(256); 77 | var at = new AccessToken(token, code.userId, code.clientId, code.scope); 78 | at.save(function(err) { 79 | if (err) { return done(err); } 80 | return done(null, token); 81 | }); 82 | }); 83 | })); 84 | ``` 85 | 86 | OAuth2orize also bundles support for password and client credential grants. 87 | Additionally, bundled refresh token support allows expired access tokens to be 88 | renewed. 89 | 90 | #### Implement Authorization Endpoint 91 | 92 | When a client requests authorization, it will redirect the user to an 93 | authorization endpoint. The server must authenticate the user and obtain 94 | their permission. 95 | 96 | ```javascript 97 | app.get('/dialog/authorize', 98 | login.ensureLoggedIn(), 99 | server.authorize(function(clientID, redirectURI, done) { 100 | Clients.findOne(clientID, function(err, client) { 101 | if (err) { return done(err); } 102 | if (!client) { return done(null, false); } 103 | if (client.redirectUri != redirectURI) { return done(null, false); } 104 | return done(null, client, client.redirectURI); 105 | }); 106 | }), 107 | function(req, res) { 108 | res.render('dialog', { transactionID: req.oauth2.transactionID, 109 | user: req.user, client: req.oauth2.client }); 110 | }); 111 | ``` 112 | 113 | In this example, [connect-ensure-login](https://github.com/jaredhanson/connect-ensure-login) 114 | middleware is being used to make sure a user is authenticated before 115 | authorization proceeds. At that point, the application renders a dialog 116 | asking the user to grant access. The resulting form submission is processed 117 | using `decision` middleware. 118 | 119 | ```javascript 120 | app.post('/dialog/authorize/decision', 121 | login.ensureLoggedIn(), 122 | server.decision()); 123 | ``` 124 | 125 | Based on the grant type requested by the client, the appropriate grant 126 | module registered above will be invoked to issue an authorization code. 127 | 128 | #### Session Serialization 129 | 130 | Obtaining the user's authorization involves multiple request/response pairs. 131 | During this time, an OAuth 2.0 transaction will be serialized to the session. 132 | Client serialization functions are registered to customize this process, which 133 | will typically be as simple as serializing the client ID, and finding the client 134 | by ID when deserializing. 135 | 136 | ```javascript 137 | server.serializeClient(function(client, done) { 138 | return done(null, client.id); 139 | }); 140 | 141 | server.deserializeClient(function(id, done) { 142 | Clients.findOne(id, function(err, client) { 143 | if (err) { return done(err); } 144 | return done(null, client); 145 | }); 146 | }); 147 | ``` 148 | 149 | #### Implement Token Endpoint 150 | 151 | Once a user has approved access, the authorization grant can be exchanged by the 152 | client for an access token. 153 | 154 | ```javascript 155 | app.post('/token', 156 | passport.authenticate(['basic', 'oauth2-client-password'], { session: false }), 157 | server.token(), 158 | server.errorHandler()); 159 | ``` 160 | 161 | [Passport](http://passportjs.org/) strategies are used to authenticate the 162 | client, in this case using either an HTTP Basic authentication header (as 163 | provided by [passport-http](https://github.com/jaredhanson/passport-http)) or 164 | client credentials in the request body (as provided by 165 | [passport-oauth2-client-password](https://github.com/jaredhanson/passport-oauth2-client-password)). 166 | 167 | Based on the grant type issued to the client, the appropriate exchange module 168 | registered above will be invoked to issue an access token. If an error occurs, 169 | `errorHandler` middleware will format an error response. 170 | 171 | #### Implement API Endpoints 172 | 173 | Once an access token has been issued, a client will use it to make API requests 174 | on behalf of the user. 175 | ```javascript 176 | app.get('/api/userinfo', 177 | passport.authenticate('bearer', { session: false }), 178 | function(req, res) { 179 | res.json(req.user); 180 | }); 181 | ``` 182 | 183 | In this example, bearer tokens are issued, which are then authenticated using 184 | an HTTP Bearer authentication header (as provided by [passport-http-bearer](https://github.com/jaredhanson/passport-http-bearer)) 185 | 186 | ## Examples 187 | 188 | This [example](https://github.com/gerges-beshay/oauth2orize-examples) demonstrates 189 | how to implement an OAuth service provider, complete with protected API access. 190 | 191 | ## Related Modules 192 | 193 | - [oauth2orize-openid](https://github.com/jaredhanson/oauth2orize-openid) — Extensions to support OpenID Connect 194 | - [oauth2orize-jwt-bearer](https://github.com/xtuple/oauth2orize-jwt-bearer) — Exchange JWT assertions for access tokens 195 | - [passport-http-bearer](https://github.com/jaredhanson/passport-http-bearer) — Bearer token authentication strategy for APIs 196 | 197 | ## Debugging 198 | 199 | oauth2orize uses the [debug module](https://www.npmjs.org/package/debug). You can enable debugging messages on the console by doing ```export DEBUG=oauth2orize``` before running your application. 200 | 201 | ## License 202 | 203 | [The MIT License](http://opensource.org/licenses/MIT) 204 | 205 | Copyright (c) 2012-2021 Jared Hanson <[https://www.jaredhanson.me/](https://www.jaredhanson.me/)> 206 | -------------------------------------------------------------------------------- /SPONSORS.md: -------------------------------------------------------------------------------- 1 | ## Sponsors 2 | 3 | - [Jared Hanson](https://github.com/jaredhanson) 4 | -------------------------------------------------------------------------------- /lib/errors/authorizationerror.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | var OAuth2Error = require('./oauth2error'); 5 | 6 | /** 7 | * `AuthorizationError` error. 8 | * 9 | * @api public 10 | */ 11 | function AuthorizationError(message, code, uri, status) { 12 | if (!status) { 13 | switch (code) { 14 | case 'invalid_request': status = 400; break; 15 | case 'unauthorized_client': status = 403; break; 16 | case 'access_denied': status = 403; break; 17 | case 'unsupported_response_type': status = 501; break; 18 | case 'invalid_scope': status = 400; break; 19 | case 'temporarily_unavailable': status = 503; break; 20 | } 21 | } 22 | 23 | OAuth2Error.call(this, message, code, uri, status); 24 | Error.captureStackTrace(this, arguments.callee); 25 | this.name = 'AuthorizationError'; 26 | } 27 | 28 | /** 29 | * Inherit from `OAuth2Error`. 30 | */ 31 | AuthorizationError.prototype.__proto__ = OAuth2Error.prototype; 32 | 33 | 34 | /** 35 | * Expose `AuthorizationError`. 36 | */ 37 | module.exports = AuthorizationError; 38 | -------------------------------------------------------------------------------- /lib/errors/badrequesterror.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `BadRequestError` error. 3 | * 4 | * @api public 5 | */ 6 | function BadRequestError(message) { 7 | Error.call(this); 8 | Error.captureStackTrace(this, arguments.callee); 9 | this.name = 'BadRequestError'; 10 | this.message = message; 11 | this.status = 400; 12 | } 13 | 14 | /** 15 | * Inherit from `Error`. 16 | */ 17 | BadRequestError.prototype.__proto__ = Error.prototype; 18 | 19 | 20 | /** 21 | * Expose `BadRequestError`. 22 | */ 23 | module.exports = BadRequestError; 24 | -------------------------------------------------------------------------------- /lib/errors/forbiddenerror.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `ForbiddenError` error. 3 | * 4 | * @api public 5 | */ 6 | function ForbiddenError(message) { 7 | Error.call(this); 8 | Error.captureStackTrace(this, arguments.callee); 9 | this.name = 'ForbiddenError'; 10 | this.message = message; 11 | this.status = 403; 12 | } 13 | 14 | /** 15 | * Inherit from `Error`. 16 | */ 17 | ForbiddenError.prototype.__proto__ = Error.prototype; 18 | 19 | 20 | /** 21 | * Expose `ForbiddenError`. 22 | */ 23 | module.exports = ForbiddenError; 24 | -------------------------------------------------------------------------------- /lib/errors/oauth2error.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `OAuth2Error` error. 3 | * 4 | * @api public 5 | */ 6 | function OAuth2Error(message, code, uri, status) { 7 | Error.call(this); 8 | this.message = message; 9 | this.code = code || 'server_error'; 10 | this.uri = uri; 11 | this.status = status || 500; 12 | } 13 | 14 | /** 15 | * Inherit from `Error`. 16 | */ 17 | OAuth2Error.prototype.__proto__ = Error.prototype; 18 | 19 | 20 | /** 21 | * Expose `OAuth2Error`. 22 | */ 23 | module.exports = OAuth2Error; 24 | -------------------------------------------------------------------------------- /lib/errors/tokenerror.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | var OAuth2Error = require('./oauth2error'); 5 | 6 | /** 7 | * `TokenError` error. 8 | * 9 | * @api public 10 | */ 11 | function TokenError(message, code, uri, status) { 12 | if (!status) { 13 | switch (code) { 14 | case 'invalid_request': status = 400; break; 15 | case 'invalid_client': status = 401; break; 16 | case 'invalid_grant': status = 403; break; 17 | case 'unauthorized_client': status = 403; break; 18 | case 'unsupported_grant_type': status = 501; break; 19 | case 'invalid_scope': status = 400; break; 20 | } 21 | } 22 | 23 | OAuth2Error.call(this, message, code, uri, status); 24 | Error.captureStackTrace(this, arguments.callee); 25 | this.name = 'TokenError'; 26 | } 27 | 28 | /** 29 | * Inherit from `OAuth2Error`. 30 | */ 31 | TokenError.prototype.__proto__ = OAuth2Error.prototype; 32 | 33 | 34 | /** 35 | * Expose `TokenError`. 36 | */ 37 | module.exports = TokenError; 38 | -------------------------------------------------------------------------------- /lib/exchange/authorizationCode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | var utils = require('../utils') 5 | , TokenError = require('../errors/tokenerror'); 6 | 7 | 8 | /** 9 | * Exchanges authorization codes for access tokens. 10 | * 11 | * This exchange middleware is used to by clients to obtain an access token by 12 | * presenting an authorization code. An authorization code must have previously 13 | * been issued, as handled by `code` grant middleware. 14 | * 15 | * Callbacks: 16 | * 17 | * This middleware requires an `issue` callback, for which the function 18 | * signature is as follows: 19 | * 20 | * function(client, code, redirectURI, done) { ... } 21 | * 22 | * `client` is the authenticated client instance attempting to obtain an access 23 | * token. `code` is the authorization code the client is in possession of. 24 | * `redirectURI` is the redirect URI specified by the client, being used as a 25 | * verifier which must match the value in the initial authorization request. 26 | * `done` is called to issue an access token: 27 | * 28 | * done(err, accessToken, refreshToken, params) 29 | * 30 | * `accessToken` is the access token that will be sent to the client. An 31 | * optional `refreshToken` will be sent to the client, if the server chooses to 32 | * implement support for this functionality. Any additional `params` will be 33 | * included in the response. If an error occurs, `done` should be invoked with 34 | * `err` set in idomatic Node.js fashion. 35 | * 36 | * Options: 37 | * 38 | * userProperty property of `req` which contains the authenticated client (default: 'user') 39 | * 40 | * Examples: 41 | * 42 | * server.exchange(oauth2orize.exchange.authorizationCode(function(client, code, redirectURI, done) { 43 | * AccessToken.create(client, code, redirectURI, function(err, accessToken) { 44 | * if (err) { return done(err); } 45 | * done(null, accessToken); 46 | * }); 47 | * })); 48 | * 49 | * References: 50 | * - [Authorization Code](http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-1.3.1) 51 | * - [Authorization Code Grant](http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-4.1) 52 | * 53 | * @param {Object} options 54 | * @param {Function} issue 55 | * @return {Function} 56 | * @api public 57 | */ 58 | module.exports = function(options, issue) { 59 | if (typeof options == 'function') { 60 | issue = options; 61 | options = undefined; 62 | } 63 | options = options || {}; 64 | 65 | if (!issue) { throw new TypeError('oauth2orize.authorizationCode exchange requires an issue callback'); } 66 | 67 | var userProperty = options.userProperty || 'user'; 68 | 69 | return function authorization_code(req, res, next) { 70 | if (!req.body) { return next(new Error('OAuth2orize requires body parsing. Did you forget app.use(express.bodyParser())?')); } 71 | 72 | // The 'user' property of `req` holds the authenticated user. In the case 73 | // of the token endpoint, the property will contain the OAuth 2.0 client. 74 | var client = req[userProperty] 75 | , code = req.body.code 76 | , redirectURI = req.body.redirect_uri; 77 | 78 | if (!code) { return next(new TokenError('Missing required parameter: code', 'invalid_request')); } 79 | 80 | function issued(err, accessToken, refreshToken, params) { 81 | if (err) { return next(err); } 82 | if (!accessToken) { return next(new TokenError('Invalid authorization code', 'invalid_grant')); } 83 | if (refreshToken && typeof refreshToken == 'object') { 84 | params = refreshToken; 85 | refreshToken = null; 86 | } 87 | 88 | var tok = {}; 89 | tok.access_token = accessToken; 90 | if (refreshToken) { tok.refresh_token = refreshToken; } 91 | if (params) { utils.merge(tok, params); } 92 | tok.token_type = tok.token_type || 'Bearer'; 93 | 94 | var json = JSON.stringify(tok); 95 | res.setHeader('Content-Type', 'application/json'); 96 | res.setHeader('Cache-Control', 'no-store'); 97 | res.setHeader('Pragma', 'no-cache'); 98 | res.end(json); 99 | } 100 | 101 | try { 102 | var arity = issue.length; 103 | if (arity == 6) { 104 | issue(client, code, redirectURI, req.body, req.authInfo, issued); 105 | } else if (arity == 5) { 106 | issue(client, code, redirectURI, req.body, issued); 107 | } else { // arity == 4 108 | issue(client, code, redirectURI, issued); 109 | } 110 | } catch (ex) { 111 | return next(ex); 112 | } 113 | }; 114 | }; 115 | -------------------------------------------------------------------------------- /lib/exchange/clientCredentials.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | var utils = require('../utils') 5 | , TokenError = require('../errors/tokenerror'); 6 | 7 | 8 | /** 9 | * Exchanges client credentials for access tokens. 10 | * 11 | * This exchange middleware is used to by clients to obtain an access token by 12 | * presenting client credentials. 13 | * 14 | * Callbacks: 15 | * 16 | * This middleware requires an `issue` callback, for which the function 17 | * signature is as follows: 18 | * 19 | * function(client, scope, body, authInfo, done) { ... } 20 | * 21 | * `client` is the authenticated client instance attempting to obtain an access 22 | * token. `scope` is the scope of access requested by the client. `done` is 23 | * called to issue an access token: 24 | * 25 | * done(err, accessToken, [refreshToken], [params]) 26 | * 27 | * `accessToken` is the access token that will be sent to the client. An 28 | * optional `refreshToken` will be sent to the client, if the server chooses to 29 | * implement support for this functionality (note that the spec says a refresh 30 | * token should not be included). Any additional `params` will be included in 31 | * the response. If an error occurs, `done` should be invoked with `err` set in 32 | * idomatic Node.js fashion. 33 | * 34 | * Options: 35 | * 36 | * userProperty property of `req` which contains the authenticated client (default: 'user') 37 | * scopeSeparator separator used to demarcate scope values (default: ' ') 38 | * 39 | * Examples: 40 | * 41 | * server.exchange(oauth2orize.exchange.clientCredentials(function(client, scope, done) { 42 | * AccessToken.create(client, scope, function(err, accessToken) { 43 | * if (err) { return done(err); } 44 | * done(null, accessToken); 45 | * }); 46 | * })); 47 | * 48 | * References: 49 | * - [Client Credentials](http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-1.3.4) 50 | * - [Client Credentials Grant](http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-4.4) 51 | * 52 | * @param {Object} options 53 | * @param {Function} issue 54 | * @return {Function} 55 | * @api public 56 | */ 57 | module.exports = function(options, issue) { 58 | if (typeof options == 'function') { 59 | issue = options; 60 | options = undefined; 61 | } 62 | options = options || {}; 63 | 64 | if (!issue) { throw new TypeError('oauth2orize.clientCredentials exchange requires an issue callback'); } 65 | 66 | var userProperty = options.userProperty || 'user'; 67 | 68 | // For maximum flexibility, multiple scope spearators can optionally be 69 | // allowed. This allows the server to accept clients that separate scope 70 | // with either space or comma (' ', ','). This violates the specification, 71 | // but achieves compatibility with existing client libraries that are already 72 | // deployed. 73 | var separators = options.scopeSeparator || ' '; 74 | if (!Array.isArray(separators)) { 75 | separators = [ separators ]; 76 | } 77 | 78 | return function client_credentials(req, res, next) { 79 | if (!req.body) { return next(new Error('OAuth2orize requires body parsing. Did you forget app.use(express.bodyParser())?')); } 80 | 81 | // The 'user' property of `req` holds the authenticated user. In the case 82 | // of the token endpoint, the property will contain the OAuth 2.0 client. 83 | var client = req[userProperty] 84 | , scope = req.body.scope; 85 | 86 | if (scope) { 87 | if (typeof scope !== 'string') { 88 | return next(new TokenError('Invalid parameter: scope must be a string', 'invalid_request')); 89 | } 90 | 91 | for (var i = 0, len = separators.length; i < len; i++) { 92 | var separated = scope.split(separators[i]); 93 | // only separate on the first matching separator. this allows for a sort 94 | // of separator "priority" (ie, favor spaces then fallback to commas) 95 | if (separated.length > 1) { 96 | scope = separated; 97 | break; 98 | } 99 | } 100 | if (!Array.isArray(scope)) { scope = [ scope ]; } 101 | } 102 | 103 | function issued(err, accessToken, refreshToken, params) { 104 | if (err) { return next(err); } 105 | if (!accessToken) { return next(new TokenError('Invalid client credentials', 'invalid_grant')); } 106 | if (refreshToken && typeof refreshToken == 'object') { 107 | params = refreshToken; 108 | refreshToken = null; 109 | } 110 | 111 | var tok = {}; 112 | tok.access_token = accessToken; 113 | if (refreshToken) { tok.refresh_token = refreshToken; } 114 | if (params) { utils.merge(tok, params); } 115 | tok.token_type = tok.token_type || 'Bearer'; 116 | 117 | var json = JSON.stringify(tok); 118 | res.setHeader('Content-Type', 'application/json'); 119 | res.setHeader('Cache-Control', 'no-store'); 120 | res.setHeader('Pragma', 'no-cache'); 121 | res.end(json); 122 | } 123 | 124 | try { 125 | var arity = issue.length; 126 | if (arity == 5) { 127 | issue(client, scope, req.body, req.authInfo, issued); 128 | } else if (arity == 4) { 129 | issue(client, scope, req.body, issued); 130 | } else if (arity == 3) { 131 | issue(client, scope, issued); 132 | } else { // arity == 2 133 | issue(client, issued); 134 | } 135 | } catch (ex) { 136 | return next(ex); 137 | } 138 | }; 139 | }; 140 | -------------------------------------------------------------------------------- /lib/exchange/password.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | var utils = require('../utils') 5 | , TokenError = require('../errors/tokenerror'); 6 | 7 | 8 | /** 9 | * Exchanges resource owner password credentials for access tokens. 10 | * 11 | * This exchange middleware is used to by clients to obtain an access token by 12 | * presenting the resource owner's password credentials. These credentials are 13 | * typically obtained directly from the user, by prompting them for input. 14 | * 15 | * Callbacks: 16 | * 17 | * This middleware requires an `issue` callback, for which the function 18 | * signature is as follows: 19 | * 20 | * function(client, username, password, scope, body, authInfo, done) { ... } 21 | * 22 | * `client` is the authenticated client instance attempting to obtain an access 23 | * token. `username` and `password` and the resource owner's credentials. 24 | * `scope` is the scope of access requested by the client. `done` is called to 25 | * issue an access token: 26 | * 27 | * done(err, accessToken, refreshToken, params) 28 | * 29 | * `accessToken` is the access token that will be sent to the client. An 30 | * optional `refreshToken` will be sent to the client, if the server chooses to 31 | * implement support for this functionality. Any additional `params` will be 32 | * included in the response. If an error occurs, `done` should be invoked with 33 | * `err` set in idomatic Node.js fashion. 34 | * 35 | * Options: 36 | * 37 | * userProperty property of `req` which contains the authenticated client (default: 'user') 38 | * scopeSeparator separator used to demarcate scope values (default: ' ') 39 | * 40 | * Examples: 41 | * 42 | * server.exchange(oauth2orize.exchange.password(function(client, username, password, scope, done) { 43 | * AccessToken.create(client, username, password, scope, function(err, accessToken) { 44 | * if (err) { return done(err); } 45 | * done(null, accessToken); 46 | * }); 47 | * })); 48 | * 49 | * References: 50 | * - [Resource Owner Password Credentials](http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-1.3.3) 51 | * - [Resource Owner Password Credentials Grant](http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-4.3) 52 | * 53 | * @param {Object} options 54 | * @param {Function} issue 55 | * @return {Function} 56 | * @api public 57 | */ 58 | module.exports = function(options, issue) { 59 | if (typeof options == 'function') { 60 | issue = options; 61 | options = undefined; 62 | } 63 | options = options || {}; 64 | 65 | if (!issue) { throw new TypeError('oauth2orize.password exchange requires an issue callback'); } 66 | 67 | var userProperty = options.userProperty || 'user'; 68 | 69 | // For maximum flexibility, multiple scope spearators can optionally be 70 | // allowed. This allows the server to accept clients that separate scope 71 | // with either space or comma (' ', ','). This violates the specification, 72 | // but achieves compatibility with existing client libraries that are already 73 | // deployed. 74 | var separators = options.scopeSeparator || ' '; 75 | if (!Array.isArray(separators)) { 76 | separators = [ separators ]; 77 | } 78 | 79 | return function password(req, res, next) { 80 | if (!req.body) { return next(new Error('OAuth2orize requires body parsing. Did you forget app.use(express.bodyParser())?')); } 81 | 82 | // The 'user' property of `req` holds the authenticated user. In the case 83 | // of the token endpoint, the property will contain the OAuth 2.0 client. 84 | var client = req[userProperty] 85 | , username = req.body.username 86 | , passwd = req.body.password 87 | , scope = req.body.scope; 88 | 89 | if (!username) { return next(new TokenError('Missing required parameter: username', 'invalid_request')); } 90 | if (!passwd) { return next(new TokenError('Missing required parameter: password', 'invalid_request')); } 91 | 92 | if (scope) { 93 | if (typeof scope !== 'string') { 94 | return next(new TokenError('Invalid parameter: scope must be a string', 'invalid_request')); 95 | } 96 | 97 | for (var i = 0, len = separators.length; i < len; i++) { 98 | var separated = scope.split(separators[i]); 99 | // only separate on the first matching separator. this allows for a sort 100 | // of separator "priority" (ie, favor spaces then fallback to commas) 101 | if (separated.length > 1) { 102 | scope = separated; 103 | break; 104 | } 105 | } 106 | if (!Array.isArray(scope)) { scope = [ scope ]; } 107 | } 108 | 109 | function issued(err, accessToken, refreshToken, params) { 110 | if (err) { return next(err); } 111 | if (!accessToken) { return next(new TokenError('Invalid resource owner credentials', 'invalid_grant')); } 112 | if (refreshToken && typeof refreshToken == 'object') { 113 | params = refreshToken; 114 | refreshToken = null; 115 | } 116 | 117 | var tok = {}; 118 | tok.access_token = accessToken; 119 | if (refreshToken) { tok.refresh_token = refreshToken; } 120 | if (params) { utils.merge(tok, params); } 121 | tok.token_type = tok.token_type || 'Bearer'; 122 | 123 | var json = JSON.stringify(tok); 124 | res.setHeader('Content-Type', 'application/json'); 125 | res.setHeader('Cache-Control', 'no-store'); 126 | res.setHeader('Pragma', 'no-cache'); 127 | res.end(json); 128 | } 129 | 130 | try { 131 | var arity = issue.length; 132 | if (arity == 7) { 133 | issue(client, username, passwd, scope, req.body, req.authInfo, issued); 134 | } else if (arity == 6) { 135 | issue(client, username, passwd, scope, req.body, issued); 136 | } else if (arity == 5) { 137 | issue(client, username, passwd, scope, issued); 138 | } else { // arity == 4 139 | issue(client, username, passwd, issued); 140 | } 141 | } catch (ex) { 142 | return next(ex); 143 | } 144 | }; 145 | }; 146 | -------------------------------------------------------------------------------- /lib/exchange/refreshToken.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | var utils = require('../utils') 5 | , TokenError = require('../errors/tokenerror'); 6 | 7 | 8 | /** 9 | * Refresh previously issued access tokens. 10 | * 11 | * This exchange middleware is used to by clients to refresh an access token by 12 | * using a refresh token issued by the server. 13 | * 14 | * Callbacks: 15 | * 16 | * This middleware requires an `issue` callback, for which the function 17 | * signature is as follows: 18 | * 19 | * function(client, refreshToken, scope, done) { ... } 20 | * 21 | * `client` is the authenticated client instance attempting to obtain an access 22 | * token. `refreshToken` is the refresh token issued by the server. `scope` is 23 | * the scope of access requested by the client, which must not include any scope 24 | * not originally granted. `done` is called to issue an access token: 25 | * 26 | * done(err, accessToken, refreshToken, params) 27 | * 28 | * `accessToken` is the access token that will be sent to the client. An 29 | * optional `refreshToken` will be sent to the client, if the server chooses to 30 | * implement support for this functionality. Any additional `params` will be 31 | * included in the response. If an error occurs, `done` should be invoked with 32 | * `err` set in idomatic Node.js fashion. 33 | * 34 | * Options: 35 | * 36 | * userProperty property of `req` which contains the authenticated client (default: 'user') 37 | * scopeSeparator separator used to demarcate scope values (default: ' ') 38 | * 39 | * Examples: 40 | * 41 | * server.exchange(oauth2orize.exchange.refreshToken(function(client, refreshToken, scope, done) { 42 | * AccessToken.create(client, refreshToken, scope, function(err, accessToken) { 43 | * if (err) { return done(err); } 44 | * done(null, accessToken); 45 | * }); 46 | * })); 47 | * 48 | * References: 49 | * - [Refreshing an Access Token](http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-6) 50 | * 51 | * @param {Object} options 52 | * @param {Function} issue 53 | * @api public 54 | */ 55 | module.exports = function(options, issue) { 56 | if (typeof options == 'function') { 57 | issue = options; 58 | options = undefined; 59 | } 60 | options = options || {}; 61 | 62 | if (!issue) { throw new TypeError('oauth2orize.refreshToken exchange requires an issue callback'); } 63 | 64 | var userProperty = options.userProperty || 'user'; 65 | 66 | // For maximum flexibility, multiple scope spearators can optionally be 67 | // allowed. This allows the server to accept clients that separate scope 68 | // with either space or comma (' ', ','). This violates the specification, 69 | // but achieves compatibility with existing client libraries that are already 70 | // deployed. 71 | var separators = options.scopeSeparator || ' '; 72 | if (!Array.isArray(separators)) { 73 | separators = [ separators ]; 74 | } 75 | 76 | return function refresh_token(req, res, next) { 77 | if (!req.body) { return next(new Error('OAuth2orize requires body parsing. Did you forget app.use(express.bodyParser())?')); } 78 | 79 | // The 'user' property of `req` holds the authenticated user. In the case 80 | // of the token endpoint, the property will contain the OAuth 2.0 client. 81 | var client = req[userProperty] 82 | , refreshToken = req.body.refresh_token 83 | , scope = req.body.scope; 84 | 85 | if (!refreshToken) { return next(new TokenError('Missing required parameter: refresh_token', 'invalid_request')); } 86 | 87 | if (scope) { 88 | if (typeof scope !== 'string') { 89 | return next(new TokenError('Invalid parameter: scope must be a string', 'invalid_request')); 90 | } 91 | 92 | for (var i = 0, len = separators.length; i < len; i++) { 93 | var separated = scope.split(separators[i]); 94 | // only separate on the first matching separator. this allows for a sort 95 | // of separator "priority" (ie, favor spaces then fallback to commas) 96 | if (separated.length > 1) { 97 | scope = separated; 98 | break; 99 | } 100 | } 101 | if (!Array.isArray(scope)) { scope = [ scope ]; } 102 | } 103 | 104 | function issued(err, accessToken, refreshToken, params) { 105 | if (err) { return next(err); } 106 | if (!accessToken) { return next(new TokenError('Invalid refresh token', 'invalid_grant')); } 107 | if (refreshToken && typeof refreshToken == 'object') { 108 | params = refreshToken; 109 | refreshToken = null; 110 | } 111 | 112 | var tok = {}; 113 | tok.access_token = accessToken; 114 | if (refreshToken) { tok.refresh_token = refreshToken; } 115 | if (params) { utils.merge(tok, params); } 116 | tok.token_type = tok.token_type || 'Bearer'; 117 | 118 | var json = JSON.stringify(tok); 119 | res.setHeader('Content-Type', 'application/json'); 120 | res.setHeader('Cache-Control', 'no-store'); 121 | res.setHeader('Pragma', 'no-cache'); 122 | res.end(json); 123 | } 124 | 125 | try { 126 | var arity = issue.length; 127 | if (arity == 6) { 128 | issue(client, refreshToken, scope, req.body, req.authInfo, issued); 129 | } else if (arity == 5) { 130 | issue(client, refreshToken, scope, req.body, issued); 131 | } else if (arity == 4) { 132 | issue(client, refreshToken, scope, issued); 133 | } else { // arity == 3 134 | issue(client, refreshToken, issued); 135 | } 136 | } catch (ex) { 137 | return next(ex); 138 | } 139 | }; 140 | }; 141 | -------------------------------------------------------------------------------- /lib/grant/code.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | var url = require('url') 5 | , utils = require('../utils') 6 | , AuthorizationError = require('../errors/authorizationerror'); 7 | 8 | 9 | /** 10 | * Handles requests to obtain a grant in the form of an authorization code. 11 | * 12 | * Callbacks: 13 | * 14 | * This middleware requires an `issue` callback, for which the function 15 | * signature is as follows: 16 | * 17 | * function(client, redirectURI, user, ares, done) { ... } 18 | * 19 | * `client` is the client instance making the authorization request. 20 | * `redirectURI` is the redirect URI specified by the client, and used as a 21 | * verifier in the subsequent access token exchange. `user` is the 22 | * authenticated user approving the request. `ares` is any additional 23 | * parameters parsed from the user's decision, including scope, duration of 24 | * access, etc. `done` is called to issue an authorization code: 25 | * 26 | * done(err, code) 27 | * 28 | * `code` is the code that will be sent to the client. If an error occurs, 29 | * `done` should be invoked with `err` set in idomatic Node.js fashion. 30 | * 31 | * The code issued in this step will be used by the client in exchange for an 32 | * access token. This code is bound to the client identifier and redirection 33 | * URI, which is included in the token request for verification. The code is a 34 | * single-use token, and should expire shortly after it is issued (the maximum 35 | * recommended lifetime is 10 minutes). 36 | * 37 | * Options: 38 | * 39 | * scopeSeparator separator used to demarcate scope values (default: ' ') 40 | * 41 | * Examples: 42 | * 43 | * server.grant(oauth2orize.grant.code(function(client, redirectURI, user, ares, done) { 44 | * AuthorizationCode.create(client.id, redirectURI, user.id, ares.scope, function(err, code) { 45 | * if (err) { return done(err); } 46 | * done(null, code); 47 | * }); 48 | * })); 49 | * 50 | * References: 51 | * - [Authorization Code](http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-1.3.1) 52 | * - [Authorization Code Grant](http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-4.1) 53 | * 54 | * @param {Object} options 55 | * @param {Function} issue 56 | * @return {Object} module 57 | * @api public 58 | */ 59 | module.exports = function code(options, issue, extend) { 60 | if (typeof options == 'function') { 61 | extend = issue; 62 | issue = options; 63 | options = undefined; 64 | } 65 | options = options || {}; 66 | extend = extend || function(txn, cb){ cb(); }; 67 | 68 | if (!issue) { throw new TypeError('oauth2orize.code grant requires an issue callback'); } 69 | 70 | var modes = options.modes || {}; 71 | if (!modes.query) { 72 | modes.query = require('../response/query'); 73 | } 74 | 75 | // For maximum flexibility, multiple scope spearators can optionally be 76 | // allowed. This allows the server to accept clients that separate scope 77 | // with either space or comma (' ', ','). This violates the specification, 78 | // but achieves compatibility with existing client libraries that are already 79 | // deployed. 80 | var separators = options.scopeSeparator || ' '; 81 | if (!Array.isArray(separators)) { 82 | separators = [ separators ]; 83 | } 84 | 85 | 86 | /* Parse requests that request `code` as `response_type`. 87 | * 88 | * @param {http.ServerRequest} req 89 | * @api public 90 | */ 91 | function request(req) { 92 | var clientID = req.query.client_id 93 | , redirectURI = req.query.redirect_uri 94 | , scope = req.query.scope 95 | , state = req.query.state; 96 | 97 | if (!clientID) { throw new AuthorizationError('Missing required parameter: client_id', 'invalid_request'); } 98 | if (typeof clientID !== 'string') { throw new AuthorizationError('Invalid parameter: client_id must be a string', 'invalid_request'); } 99 | 100 | if (scope) { 101 | if (typeof scope !== 'string') { 102 | throw new AuthorizationError('Invalid parameter: scope must be a string', 'invalid_request'); 103 | } 104 | 105 | for (var i = 0, len = separators.length; i < len; i++) { 106 | var separated = scope.split(separators[i]); 107 | // only separate on the first matching separator. this allows for a sort 108 | // of separator "priority" (ie, favor spaces then fallback to commas) 109 | if (separated.length > 1) { 110 | scope = separated; 111 | break; 112 | } 113 | } 114 | 115 | if (!Array.isArray(scope)) { scope = [ scope ]; } 116 | } 117 | 118 | return { 119 | clientID: clientID, 120 | redirectURI: redirectURI, 121 | scope: scope, 122 | state: state 123 | }; 124 | } 125 | 126 | /* Sends responses to transactions that request `code` as `response_type`. 127 | * 128 | * @param {Object} txn 129 | * @param {http.ServerResponse} res 130 | * @param {Function} next 131 | * @api public 132 | */ 133 | function response(txn, res, complete, next) { 134 | var mode = 'query' 135 | , respond; 136 | if (txn.req && txn.req.responseMode) { 137 | mode = txn.req.responseMode; 138 | } 139 | respond = modes[mode]; 140 | 141 | if (!respond) { 142 | // http://lists.openid.net/pipermail/openid-specs-ab/Week-of-Mon-20140317/004680.html 143 | return next(new AuthorizationError('Unsupported response mode: ' + mode, 'unsupported_response_mode', null, 501)); 144 | } 145 | if (respond && respond.validate) { 146 | try { 147 | respond.validate(txn); 148 | } catch(ex) { 149 | return next(ex); 150 | } 151 | } 152 | 153 | if (!txn.res.allow) { 154 | var params = { error: 'access_denied' }; 155 | if (txn.req && txn.req.state) { params.state = txn.req.state; } 156 | return respond(txn, res, params); 157 | } 158 | 159 | function issued(err, code) { 160 | if (err) { return next(err); } 161 | if (!code) { return next(new AuthorizationError('Request denied by authorization server', 'access_denied')); } 162 | 163 | var params = { code: code }; 164 | if (txn.req && txn.req.state) { params.state = txn.req.state; } 165 | extend(txn, function(err, exparams) { 166 | if (err) { return next(err); } 167 | if (exparams) { utils.merge(params, exparams); } 168 | complete(function(err) { 169 | if (err) { return next(err); } 170 | return respond(txn, res, params); 171 | }); 172 | }); 173 | } 174 | 175 | // NOTE: The `redirect_uri`, if present in the client's authorization 176 | // request, must also be present in the subsequent request to exchange 177 | // the authorization code for an access token. Acting as a verifier, 178 | // the two values must be equal and serve to protect against certain 179 | // types of attacks. More information can be found here: 180 | // 181 | // http://hueniverse.com/2011/06/oauth-2-0-redirection-uri-validation/ 182 | 183 | try { 184 | var arity = issue.length; 185 | if (arity == 7) { 186 | issue(txn.client, txn.req.redirectURI, txn.user, txn.res, txn.req, txn.locals, issued); 187 | } else if (arity == 6) { 188 | issue(txn.client, txn.req.redirectURI, txn.user, txn.res, txn.req, issued); 189 | } else if (arity == 5) { 190 | issue(txn.client, txn.req.redirectURI, txn.user, txn.res, issued); 191 | } else { // arity == 4 192 | issue(txn.client, txn.req.redirectURI, txn.user, issued); 193 | } 194 | } catch (ex) { 195 | return next(ex); 196 | } 197 | } 198 | 199 | function errorHandler(err, txn, res, next) { 200 | var mode = 'query' 201 | , params = {} 202 | , respond; 203 | if (txn.req && txn.req.responseMode) { 204 | mode = txn.req.responseMode; 205 | } 206 | respond = modes[mode]; 207 | 208 | if (!respond) { 209 | return next(err); 210 | } 211 | if (respond && respond.validate) { 212 | try { 213 | respond.validate(txn); 214 | } catch(ex) { 215 | return next(err); 216 | } 217 | } 218 | 219 | params.error = err.code || 'server_error'; 220 | if (err.message) { params.error_description = err.message; } 221 | if (err.uri) { params.error_uri = err.uri; } 222 | if (txn.req && txn.req.state) { params.state = txn.req.state; } 223 | return respond(txn, res, params); 224 | } 225 | 226 | 227 | /** 228 | * Return `code` approval module. 229 | */ 230 | var mod = {}; 231 | mod.name = 'code'; 232 | mod.request = request; 233 | mod.response = response; 234 | mod.error = errorHandler; 235 | return mod; 236 | }; 237 | -------------------------------------------------------------------------------- /lib/grant/token.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | var url = require('url') 5 | , qs = require('querystring') 6 | , utils = require('../utils') 7 | , AuthorizationError = require('../errors/authorizationerror'); 8 | 9 | 10 | /** 11 | * Handles requests to obtain an implicit grant. 12 | * 13 | * Callbacks: 14 | * 15 | * This middleware requires an `issue` callback, for which the function 16 | * signature is as follows: 17 | * 18 | * function(client, user, ares, done) { ... } 19 | * 20 | * `client` is the client instance making the authorization request. `user` is 21 | * the authenticated user approving the request. `ares` is any additional 22 | * parameters parsed from the user's decision, including scope, duration of 23 | * access, etc. `done` is called to issue an access token: 24 | * 25 | * done(err, accessToken, params) 26 | * 27 | * `accessToken` is the access token that will be sent to the client. 28 | * Optionally, any additional `params` will be included in the response. If an 29 | * error occurs, `done` should be invoked with `err` set in idomatic Node.js 30 | * fashion. 31 | * 32 | * Implicit grants do not include client authentication, and rely on the 33 | * registration of the redirect URI. Applications can enforce this constraint 34 | * in the `validate` callback of `authorization` middleware. 35 | * 36 | * Options: 37 | * 38 | * scopeSeparator separator used to demarcate scope values (default: ' ') 39 | * 40 | * Examples: 41 | * 42 | * server.grant(oauth2orize.grant.token(function(client, user, ares, done) {} 43 | * AccessToken.create(client, user, ares.scope, function(err, accessToken) { 44 | * if (err) { return done(err); } 45 | * done(null, accessToken); 46 | * }); 47 | * })); 48 | * 49 | * References: 50 | * - [Implicit](http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-1.3.2) 51 | * - [Implicit Grant](http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-4.2) 52 | * 53 | * @param {Object} options 54 | * @param {Function} issue 55 | * @return {Object} module 56 | * @api public 57 | */ 58 | module.exports = function token(options, issue) { 59 | if (typeof options == 'function') { 60 | issue = options; 61 | options = undefined; 62 | } 63 | options = options || {}; 64 | 65 | if (!issue) { throw new TypeError('oauth2orize.token grant requires an issue callback'); } 66 | 67 | var modes = options.modes || {}; 68 | if (!modes.fragment) { 69 | modes.fragment = require('../response/fragment'); 70 | } 71 | 72 | // For maximum flexibility, multiple scope spearators can optionally be 73 | // allowed. This allows the server to accept clients that separate scope 74 | // with either space or comma (' ', ','). This violates the specification, 75 | // but achieves compatibility with existing client libraries that are already 76 | // deployed. 77 | var separators = options.scopeSeparator || ' '; 78 | if (!Array.isArray(separators)) { 79 | separators = [ separators ]; 80 | } 81 | 82 | 83 | /* Parse requests that request `token` as `response_type`. 84 | * 85 | * @param {http.ServerRequest} req 86 | * @api public 87 | */ 88 | function request(req) { 89 | var clientID = req.query.client_id 90 | , redirectURI = req.query.redirect_uri 91 | , scope = req.query.scope 92 | , state = req.query.state; 93 | 94 | if (!clientID) { throw new AuthorizationError('Missing required parameter: client_id', 'invalid_request'); } 95 | if (typeof clientID !== 'string') { throw new AuthorizationError('Invalid parameter: client_id must be a string', 'invalid_request'); } 96 | 97 | if (scope) { 98 | if (typeof scope !== 'string') { 99 | throw new AuthorizationError('Invalid parameter: scope must be a string', 'invalid_request'); 100 | } 101 | 102 | for (var i = 0, len = separators.length; i < len; i++) { 103 | var separated = scope.split(separators[i]); 104 | // only separate on the first matching separator. this allows for a sort 105 | // of separator "priority" (ie, favor spaces then fallback to commas) 106 | if (separated.length > 1) { 107 | scope = separated; 108 | break; 109 | } 110 | } 111 | 112 | if (!Array.isArray(scope)) { scope = [ scope ]; } 113 | } 114 | 115 | return { 116 | clientID: clientID, 117 | redirectURI: redirectURI, 118 | scope: scope, 119 | state: state 120 | }; 121 | } 122 | 123 | /* Sends responses to transactions that request `token` as `response_type`. 124 | * 125 | * @param {Object} txn 126 | * @param {http.ServerResponse} res 127 | * @param {Function} next 128 | * @api public 129 | */ 130 | function response(txn, res, complete, next) { 131 | var mode = 'fragment' 132 | , respond; 133 | if (txn.req && txn.req.responseMode) { 134 | mode = txn.req.responseMode; 135 | } 136 | respond = modes[mode]; 137 | 138 | if (!respond) { 139 | // http://lists.openid.net/pipermail/openid-specs-ab/Week-of-Mon-20140317/004680.html 140 | return next(new AuthorizationError('Unsupported response mode: ' + mode, 'unsupported_response_mode', null, 501)); 141 | } 142 | if (respond && respond.validate) { 143 | try { 144 | respond.validate(txn); 145 | } catch(ex) { 146 | return next(ex); 147 | } 148 | } 149 | 150 | if (!txn.res.allow) { 151 | var params = { error: 'access_denied' }; 152 | if (txn.req && txn.req.state) { params.state = txn.req.state; } 153 | return respond(txn, res, params); 154 | } 155 | 156 | function issued(err, accessToken, params) { 157 | if (err) { return next(err); } 158 | if (!accessToken) { return next(new AuthorizationError('Request denied by authorization server', 'access_denied')); } 159 | 160 | var tok = {}; 161 | tok.access_token = accessToken; 162 | if (params) { utils.merge(tok, params); } 163 | tok.token_type = tok.token_type || 'Bearer'; 164 | if (txn.req && txn.req.state) { tok.state = txn.req.state; } 165 | complete(function(err) { 166 | if (err) { return next(err); } 167 | return respond(txn, res, tok); 168 | }); 169 | } 170 | 171 | // NOTE: In contrast to an authorization code grant, redirectURI is not 172 | // passed as an argument to the issue callback because it is not used 173 | // as a verifier in a subsequent token exchange. However, when 174 | // issuing an implicit access tokens, an application must ensure that 175 | // the redirection URI is registered, which can be done in the 176 | // `validate` callback of `authorization` middleware. 177 | 178 | try { 179 | var arity = issue.length; 180 | if (arity == 6) { 181 | issue(txn.client, txn.user, txn.res, txn.req, txn.locals, issued); 182 | } else if (arity == 5) { 183 | issue(txn.client, txn.user, txn.res, txn.req, issued); 184 | } else if (arity == 4) { 185 | issue(txn.client, txn.user, txn.res, issued); 186 | } else { // arity == 3 187 | issue(txn.client, txn.user, issued); 188 | } 189 | } catch (ex) { 190 | return next(ex); 191 | } 192 | } 193 | 194 | function errorHandler(err, txn, res, next) { 195 | var mode = 'fragment' 196 | , params = {} 197 | , respond; 198 | if (txn.req && txn.req.responseMode) { 199 | mode = txn.req.responseMode; 200 | } 201 | respond = modes[mode]; 202 | 203 | if (!respond) { 204 | return next(err); 205 | } 206 | if (respond && respond.validate) { 207 | try { 208 | respond.validate(txn); 209 | } catch(ex) { 210 | return next(err); 211 | } 212 | } 213 | 214 | params.error = err.code || 'server_error'; 215 | if (err.message) { params.error_description = err.message; } 216 | if (err.uri) { params.error_uri = err.uri; } 217 | if (txn.req && txn.req.state) { params.state = txn.req.state; } 218 | return respond(txn, res, params); 219 | } 220 | 221 | 222 | /** 223 | * Return `token` approval module. 224 | */ 225 | var mod = {}; 226 | mod.name = 'token'; 227 | mod.request = request; 228 | mod.response = response; 229 | mod.error = errorHandler; 230 | return mod; 231 | }; 232 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | var fs = require('fs') 5 | , path = require('path') 6 | , Server = require('./server'); 7 | 8 | 9 | /** 10 | * Create an OAuth 2.0 server. 11 | * 12 | * @return {Server} 13 | * @api public 14 | */ 15 | function createServer(options) { 16 | var server = new Server(options); 17 | return server; 18 | } 19 | 20 | // expose createServer() as the module 21 | exports = module.exports = createServer; 22 | 23 | /** 24 | * Export `.createServer()`. 25 | */ 26 | exports.createServer = createServer; 27 | 28 | 29 | /** 30 | * Export middleware. 31 | */ 32 | exports.errorHandler = require('./middleware/errorHandler'); 33 | 34 | /** 35 | * Auto-load bundled grants. 36 | */ 37 | exports.grant = {}; 38 | 39 | fs.readdirSync(__dirname + '/grant').forEach(function(filename) { 40 | if (/\.js$/.test(filename)) { 41 | var name = path.basename(filename, '.js'); 42 | var load = function () { return require('./grant/' + name); }; 43 | exports.grant.__defineGetter__(name, load); 44 | } 45 | }); 46 | 47 | // alias grants 48 | exports.grant.authorizationCode = exports.grant.code; 49 | exports.grant.implicit = exports.grant.token; 50 | 51 | /** 52 | * Auto-load bundled exchanges. 53 | */ 54 | exports.exchange = {}; 55 | 56 | fs.readdirSync(__dirname + '/exchange').forEach(function(filename) { 57 | if (/\.js$/.test(filename)) { 58 | var name = path.basename(filename, '.js'); 59 | var load = function () { return require('./exchange/' + name); }; 60 | exports.exchange.__defineGetter__(name, load); 61 | } 62 | }); 63 | 64 | // alias exchanges 65 | exports.exchange.code = exports.exchange.authorizationCode; 66 | 67 | /** 68 | * Export errors. 69 | */ 70 | exports.OAuth2Error = require('./errors/oauth2error'); 71 | exports.AuthorizationError = require('./errors/authorizationerror'); 72 | exports.TokenError = require('./errors/tokenerror'); 73 | -------------------------------------------------------------------------------- /lib/middleware/authorization.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | var utils = require('../utils') 5 | , AuthorizationError = require('../errors/authorizationerror'); 6 | 7 | 8 | /** 9 | * Handle authorization requests from OAuth 2.0 clients. 10 | * 11 | * Obtaining authorization via OAuth 2.0 consists of a sequence of discrete 12 | * steps. First, the client requests authorization from the user (in this case 13 | * using an authorization server as an intermediary). The authorization server 14 | * conducts an approval dialog with the user to obtain permission. After access 15 | * has been allowed, a grant is issued to the client which can be exchanged for 16 | * an access token. 17 | * 18 | * This middleware is used to initiate authorization transactions. If a request 19 | * is parsed and validated, the following properties will be available on the 20 | * request: 21 | * 22 | * req.oauth2.transactionID an ID assigned to this transaction 23 | * req.oauth2.client client requesting the user's authorization 24 | * req.oauth2.redirectURI URL to redirect the user to after authorization 25 | * req.oauth2.req parameters from request made by the client 26 | * 27 | * The contents of `req.oauth2.req` depends on the grant type requested by the 28 | * the client. The `server`'s request parsing functions are used to construct 29 | * this object, and the application can implement support for these types as 30 | * necessary, taking advantage of bundled grant middleware. 31 | * 32 | * Because the approval dialog may be conducted over a series of requests and 33 | * responses, a transaction is also stored in the session until a decision is 34 | * reached. The application is responsible for verifying the user's identity 35 | * and prompting him or her to allow or deny the request (typically via an HTML 36 | * form). At that point, `decision` middleware can be utilized to process the 37 | * user's decision and issue the grant to the client. 38 | * 39 | * Callbacks: 40 | * 41 | * This middleware requires a `validate` callback, for which the function 42 | * signature is as follows: 43 | * 44 | * function(clientID, redirectURI, done) { ... } 45 | * 46 | * `clientID` is the client identifier and `redirectURI` is the redirect URI as 47 | * indicated by the client. If the request is valid, `done` must be invoked 48 | * with the following signature: 49 | * 50 | * done(err, client, redirectURI); 51 | * 52 | * `client` is the client instance which is making the request. `redirectURI` 53 | * is the URL to which the user will be redirected after authorization is 54 | * obtained (which may be different, if the server is enforcing registration 55 | * requirements). If an error occurs, `done` should be invoked with `err` set 56 | * in idomatic Node.js fashion. 57 | * 58 | * Alternate function signatures of the `validate` callback are available if 59 | * needed. Consult the source code for a definitive reference. 60 | * 61 | * 62 | * Note that authorization may be obtained by the client directly from the user 63 | * without using an authorization server as an intermediary (for example, when 64 | * obtaining a grant in the form of the user's password credentials). In these 65 | * cases, the client interacts only with the token endpoint without any need to 66 | * interact with the authorization endpoint. 67 | * 68 | * Options: 69 | * 70 | * idLength length of generated transaction IDs (default: 8) 71 | * sessionKey key under which transactions are stored in the session (default: 'authorize') 72 | * 73 | * Examples: 74 | * 75 | * app.get('/dialog/authorize', 76 | * login.ensureLoggedIn(), 77 | * server.authorization(function(clientID, redirectURI, done) { 78 | * Clients.findOne(clientID, function(err, client) { 79 | * if (err) { return done(err); } 80 | * if (!client) { return done(null, false); } 81 | * return done(null, client, client.redirectURI); 82 | * }); 83 | * }), 84 | * function(req, res) { 85 | * res.render('dialog', { transactionID: req.oauth2.transactionID, 86 | * user: req.user, client: req.oauth2.client }); 87 | * }); 88 | * 89 | * References: 90 | * - [Authorization Endpoint](http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-3.1) 91 | * 92 | * @param {Server} server 93 | * @param {Object} options 94 | * @param {Function} validate 95 | * @return {Function} 96 | * @api protected 97 | */ 98 | module.exports = function(server, options, validate, immediate, complete) { 99 | if (typeof options == 'function') { 100 | complete = immediate; 101 | immediate = validate; 102 | validate = options; 103 | options = undefined; 104 | } 105 | options = options || {}; 106 | immediate = immediate || function (client, user, done) { return done(null, false); }; 107 | 108 | if (!server) { throw new TypeError('oauth2orize.authorization middleware requires a server argument'); } 109 | if (!validate) { throw new TypeError('oauth2orize.authorization middleware requires a validate function'); } 110 | 111 | var userProperty = options.userProperty || 'user'; 112 | 113 | return function authorization(req, res, next) { 114 | 115 | var body = req.body || {} 116 | , type = req.query.response_type || body.response_type; 117 | 118 | server._parse(type, req, function(err, areq) { 119 | if (err) { return next(err); } 120 | if (!areq || !areq.type) { return next(new AuthorizationError('Missing required parameter: response_type', 'invalid_request')); } 121 | if (areq.type && !areq.clientID) { return next(new AuthorizationError('Unsupported response type: ' + type, 'unsupported_response_type')); } 122 | 123 | function validated(err, client, redirectURI, webOrigin) { 124 | // Set properties *before* next()'ing due to error. The presence of a 125 | // redirectURI being provided, even under error conditions, indicates 126 | // that the client should be informed of the error via a redirect. 127 | req.oauth2 = {}; 128 | if (client) { req.oauth2.client = client; } 129 | if (redirectURI) { req.oauth2.redirectURI = redirectURI; } 130 | if (webOrigin) { req.oauth2.webOrigin = webOrigin; } 131 | req.oauth2.req = areq; 132 | req.oauth2.user = req[userProperty]; 133 | if (req.locals) { req.oauth2.locals = req.locals; } 134 | 135 | if (err) { return next(err); } 136 | if (!client) { return next(new AuthorizationError('Unauthorized client', 'unauthorized_client')); } 137 | 138 | function immediated(err, allow, info, locals) { 139 | if (err) { return next(err); } 140 | if (allow) { 141 | req.oauth2.res = info || {}; 142 | req.oauth2.res.allow = true; 143 | if (locals) { 144 | req.oauth2.locals = req.oauth2.locals || {}; 145 | utils.merge(req.oauth2.locals, locals); 146 | } 147 | 148 | function completing(cb) { 149 | if (!complete) { return cb(); } 150 | complete(req, req.oauth2, cb); 151 | } 152 | 153 | server._respond(req.oauth2, res, completing, function(err) { 154 | if (err) { return next(err); } 155 | return next(new AuthorizationError('Unsupported response type: ' + req.oauth2.req.type, 'unsupported_response_type')); 156 | }); 157 | } else { 158 | // Add info and locals to `req.oauth2`, where they will be 159 | // available to the next middleware. Since this is a 160 | // non-immediate response, the next middleware's responsibility is 161 | // to prompt the user to allow or deny access. `info` and 162 | // `locals` are passed along as they may be of assistance when 163 | // rendering the prompt. 164 | // 165 | // Note that, when using the legacy transaction store, `info` is 166 | // also serialized into the transaction, where it can further be 167 | // utilized in the `decision` middleware after the user submits the 168 | // prompt's form. As such, `info` should be a normal JSON object, 169 | // so that it can be correctly serialized into the session. 170 | // `locals` is only carried through to the middleware chain for the 171 | // current request, so it may contain instantiated classes that 172 | // don't serialize cleanly. 173 | // 174 | // The transaction store is pluggable when initializing the `Server` 175 | // instance. If an application implements a custom transaction 176 | // store, the specific details of what properties are serialized 177 | // into the transaction and loaded on subsequent requests are 178 | // determined by the implementation. 179 | req.oauth2.info = info; 180 | if (locals) { 181 | req.oauth2.locals = req.oauth2.locals || {}; 182 | utils.merge(req.oauth2.locals, locals); 183 | } 184 | 185 | // A dialog needs to be conducted to obtain the user's approval. 186 | // Serialize a transaction to the session. The transaction will be 187 | // restored (and removed) from the session when the user allows or 188 | // denies the request. 189 | function stored(err, tid) { 190 | if (err) { return next(err); } 191 | req.oauth2.transactionID = tid; 192 | next(); 193 | } 194 | 195 | if (server._txnStore.legacy == true) { 196 | var txn = {}; 197 | txn.protocol = 'oauth2'; 198 | txn.client = client; 199 | txn.redirectURI = redirectURI; 200 | txn.webOrigin = webOrigin; 201 | txn.req = areq; 202 | txn.info = info; 203 | 204 | server._txnStore.store(server, options, req, txn, stored); 205 | } else { 206 | server._txnStore.store(req, req.oauth2, stored); 207 | } 208 | } 209 | } 210 | 211 | var arity = immediate.length; 212 | if (arity == 7) { 213 | immediate(req.oauth2.client, req.oauth2.user, req.oauth2.req.scope, req.oauth2.req.type, req.oauth2.req, req.oauth2.locals, immediated); 214 | } else if (arity == 6) { 215 | immediate(req.oauth2.client, req.oauth2.user, req.oauth2.req.scope, req.oauth2.req.type, req.oauth2.req, immediated); 216 | } else if (arity == 5) { 217 | immediate(req.oauth2.client, req.oauth2.user, req.oauth2.req.scope, req.oauth2.req.type, immediated); 218 | } else if (arity == 4) { 219 | immediate(req.oauth2.client, req.oauth2.user, req.oauth2.req.scope, immediated); 220 | } else if (arity == 3) { 221 | immediate(req.oauth2.client, req.oauth2.user, immediated); 222 | } else { // arity == 2 223 | immediate(req.oauth2, immediated); 224 | } 225 | } 226 | 227 | try { 228 | var arity = validate.length; 229 | if (arity == 5) { 230 | validate(areq.clientID, areq.redirectURI, areq.scope, areq.type, validated); 231 | } else if (arity == 4) { 232 | validate(areq.clientID, areq.redirectURI, areq.scope, validated); 233 | } else if (arity == 3) { 234 | validate(areq.clientID, areq.redirectURI, validated); 235 | } else { // arity == 2 236 | validate(areq, validated); 237 | } 238 | } catch (ex) { 239 | return next(ex); 240 | } 241 | }); 242 | }; 243 | }; 244 | -------------------------------------------------------------------------------- /lib/middleware/authorizationErrorHandler.js: -------------------------------------------------------------------------------- 1 | module.exports = function(server, options) { 2 | options = options || {}; 3 | 4 | if (!server) { throw new TypeError('oauth2orize.authorizationErrorHandler middleware requires a server argument'); } 5 | 6 | return function authorizationErrorHandler(err, req, res, next) { 7 | if (!req.oauth2) { return next(err); } 8 | 9 | if (req.oauth2.transactionID && !req.oauth2._endProxied) { 10 | // proxy end() to delete the transaction 11 | var end = res.end; 12 | res.end = function(chunk, encoding) { 13 | if (server._txnStore.legacy == true) { 14 | server._txnStore.remove(options, req, req.oauth2.transactionID, function noop(){}); 15 | } else { 16 | server._txnStore.remove(req, req.oauth2.transactionID, function noop(){}); 17 | } 18 | 19 | res.end = end; 20 | res.end(chunk, encoding); 21 | }; 22 | } 23 | 24 | server._respondError(err, req.oauth2, res, function(err) { 25 | return next(err); 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/middleware/decision.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | var utils = require('../utils') 5 | , AuthorizationError = require('../errors/authorizationerror') 6 | , ForbiddenError = require('../errors/forbiddenerror'); 7 | 8 | 9 | /** 10 | * Handle authorization decisions from resource owners. 11 | * 12 | * Obtaining authorization via OAuth 2.0 consists of a sequence of discrete 13 | * steps. First, the client requests authorization from the user (in this case 14 | * using an authorization server as an intermediary). The authorization server 15 | * conducts an approval dialog with the user to obtain permission. After access 16 | * has been allowed, a grant is issued to the client which can be exchanged for 17 | * an access token. 18 | * 19 | * This middleware is used to process a user's decision about whether to allow 20 | * or deny access. The client that initiated the authorization transaction will 21 | * be sent a response, including a grant if access was allowed. 22 | * 23 | * The exact form of the grant will depend on the type requested by the client. 24 | * The `server`'s response handling functions are used to issue the grant and 25 | * send the response. An application can implement support for these types as 26 | * necessary, including taking advantage of bundled grant middleware. 27 | * 28 | * Callbacks: 29 | * 30 | * An optional `parse` callback can be passed as an argument, for which the 31 | * function signature is as follows: 32 | * 33 | * function(req, done) { ... } 34 | * 35 | * `req` is the request, which can be parsed for any additional parameters found 36 | * in query as required by the service provider. `done` is a callback which 37 | * must be invoked with the following signature: 38 | * 39 | * done(err, params); 40 | * 41 | * `params` are the additional parameters parsed from the request. These will 42 | * be set on the transaction at `req.oauth2.res`. If an error occurs, `done` 43 | * should be invoked with `err` set in idomatic Node.js fashion. 44 | * 45 | * Options: 46 | * 47 | * cancelField name of field that is set if user denied access (default: 'cancel') 48 | * userProperty property of `req` which contains the authenticated user (default: 'user') 49 | * sessionKey key under which transactions are stored in the session (default: 'authorize') 50 | * 51 | * Examples: 52 | * 53 | * app.post('/dialog/authorize/decision', 54 | * login.ensureLoggedIn(), 55 | * server.decision()); 56 | * 57 | * app.post('/dialog/authorize/decision', 58 | * login.ensureLoggedIn(), 59 | * server.decision(function(req, done) { 60 | * return done(null, { scope: req.scope }) 61 | * })); 62 | * 63 | * @param {Server} server 64 | * @param {Object} options 65 | * @param {Function} parse 66 | * @return {Function} 67 | * @api protected 68 | */ 69 | module.exports = function(server, options, parse, complete) { 70 | if (typeof options == 'function') { 71 | complete = parse; 72 | parse = options; 73 | options = undefined; 74 | } 75 | options = options || {}; 76 | parse = parse || function(req, done) { return done(); }; 77 | 78 | if (!server) { throw new TypeError('oauth2orize.decision middleware requires a server argument'); } 79 | 80 | var cancelField = options.cancelField || 'cancel' 81 | , userProperty = options.userProperty || 'user'; 82 | 83 | return function decision(req, res, next) { 84 | if (!req.body) { return next(new Error('OAuth2orize requires body parsing. Did you forget app.use(express.bodyParser())?')); } 85 | if (!req.oauth2) { return next(new Error('OAuth2orize requires transaction support. Did you forget oauth2orize.transactionLoader(...)?')); } 86 | 87 | parse(req, function(err, ares, locals) { 88 | if (err) { return next(err); } 89 | 90 | var tid = req.oauth2.transactionID; 91 | req.oauth2.user = req[userProperty]; 92 | req.oauth2.res = ares || {}; 93 | if (locals) { 94 | req.oauth2.locals = req.oauth2.locals || {}; 95 | utils.merge(req.oauth2.locals, locals); 96 | } 97 | 98 | if (req.oauth2.res.allow === undefined) { 99 | if (!req.body[cancelField]) { req.oauth2.res.allow = true; } 100 | else { req.oauth2.res.allow = false; } 101 | } 102 | 103 | // proxy end() to delete the transaction 104 | var end = res.end; 105 | res.end = function(chunk, encoding) { 106 | if (server._txnStore.legacy == true) { 107 | server._txnStore.remove(options, req, req.oauth2.transactionID, function noop(){}); 108 | } else { 109 | server._txnStore.remove(req, req.oauth2.transactionID, function noop(){}); 110 | } 111 | 112 | res.end = end; 113 | res.end(chunk, encoding); 114 | }; 115 | req.oauth2._endProxied = true; 116 | 117 | function completing(cb) { 118 | if (!complete) { return cb(); } 119 | complete(req, req.oauth2, cb); 120 | } 121 | 122 | server._respond(req.oauth2, res, completing, function(err) { 123 | if (err) { return next(err); } 124 | return next(new AuthorizationError('Unsupported response type: ' + req.oauth2.req.type, 'unsupported_response_type')); 125 | }); 126 | }); 127 | }; 128 | }; 129 | -------------------------------------------------------------------------------- /lib/middleware/errorHandler.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | var url = require('url') 5 | , qs = require('querystring') 6 | , UnorderedList = require('../unorderedlist'); 7 | 8 | 9 | /** 10 | * Handles errors encountered in OAuth 2.0 endpoints. 11 | * 12 | * This is error handling middleware intended for use in endpoints involved in 13 | * the OAuth 2.0 protocol. If an error occurs while processing a request, this 14 | * middleware formats a response in accordance with the OAuth 2.0 specification. 15 | * 16 | * This middleware has two modes of operation: direct and indirect. Direct mode 17 | * (the default) is intended to be used with the token endpoint, in which the 18 | * response can be sent directly to the client. Indirect mode is intended to be 19 | * used with user authorization endpoints, in which the response must be issued 20 | * to the client indirectly via a redirect through the user's browser. 21 | * 22 | * Options: 23 | * - `mode` mode of operation, defaults to `direct` 24 | * 25 | * Examples: 26 | * 27 | * app.post('/token', 28 | * passport.authenticate(['basic', 'oauth2-client-password'], { session: false }), 29 | * server.token(), 30 | * server.errorHandler()); 31 | * 32 | * app.get('/dialog/authorize', 33 | * login.ensureLoggedIn(), 34 | * server.authorization( ... ) 35 | * server.errorHandler({ mode: 'indirect' })); 36 | * 37 | * References: 38 | * - [Error Response](http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-5.2) 39 | * - [Authorization Response](http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-4.1.2) 40 | * - [Authorization Response](http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-4.2.2) 41 | * 42 | * @param {Object} options 43 | * @return {Function} 44 | * @api public 45 | */ 46 | module.exports = function(options) { 47 | options = options || {}; 48 | 49 | var mode = options.mode || 'direct' 50 | , fragment = options.fragment || ['token'] 51 | , modes = options.modes || {}; 52 | 53 | if (!modes.query) { 54 | modes.query = require('../response/query'); 55 | } 56 | if (!modes.fragment) { 57 | modes.fragment = require('../response/fragment'); 58 | } 59 | 60 | return function errorHandler(err, req, res, next) { 61 | if (mode == 'direct') { 62 | if (err.status) { res.statusCode = err.status; } 63 | if (!res.statusCode || res.statusCode < 400) { res.statusCode = 500; } 64 | 65 | if (res.statusCode == 401) { 66 | // TODO: set WWW-Authenticate header 67 | } 68 | 69 | var e = {}; 70 | e.error = err.code || 'server_error'; 71 | if (err.message) { e.error_description = err.message; } 72 | if (err.uri) { e.error_uri = err.uri; } 73 | 74 | res.setHeader('Content-Type', 'application/json'); 75 | return res.end(JSON.stringify(e)); 76 | } else if (mode == 'indirect') { 77 | // If the redirectURI for this OAuth 2.0 transaction is invalid, the user 78 | // agent will not be redirected and the client will not be informed. `next` 79 | // immediately into the application's error handler, so a message can be 80 | // displayed to the user. 81 | if (!req.oauth2 || !req.oauth2.redirectURI) { return next(err); } 82 | 83 | var enc = 'query'; 84 | if (req.oauth2.req) { 85 | var type = new UnorderedList(req.oauth2.req.type); 86 | // In accordance with [OAuth 2.0 Multiple Response Type Encoding 87 | // Practices - draft 08](http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html), 88 | // if the response type contains any value that requires fragment 89 | // encoding, the response will be fragment encoded. 90 | if (type.containsAny(fragment)) { enc = 'fragment'; } 91 | if (req.oauth2.req.responseMode) { 92 | // Encode the response using the requested mode, if specified. 93 | enc = req.oauth2.req.responseMode; 94 | } 95 | } 96 | 97 | var respond = modes[enc] 98 | , params = {}; 99 | 100 | if (!respond) { return next(err); } 101 | 102 | params.error = err.code || 'server_error'; 103 | if (err.message) { params.error_description = err.message; } 104 | if (err.uri) { params.error_uri = err.uri; } 105 | if (req.oauth2.req && req.oauth2.req.state) { params.state = req.oauth2.req.state; } 106 | return respond(req.oauth2, res, params); 107 | } else { 108 | return next(err); 109 | } 110 | }; 111 | }; 112 | -------------------------------------------------------------------------------- /lib/middleware/resume.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | var utils = require('../utils') 5 | , AuthorizationError = require('../errors/authorizationerror'); 6 | 7 | 8 | module.exports = function(server, options, immediate, complete) { 9 | if (typeof options == 'function') { 10 | complete = immediate; 11 | immediate = options; 12 | options = undefined; 13 | } 14 | options = options || {}; 15 | 16 | if (!server) { throw new TypeError('oauth2orize.resume middleware requires a server argument'); } 17 | if (!immediate) { throw new TypeError('oauth2orize.resume middleware requires an immediate function'); } 18 | 19 | var userProperty = options.userProperty || 'user'; 20 | 21 | return function resume(req, res, next) { 22 | if (!req.oauth2) { return next(new Error('OAuth2orize requires transaction support. Did you forget oauth2orize.transactionLoader(...)?')); } 23 | 24 | req.oauth2.user = req[userProperty]; 25 | if (res.locals) { 26 | req.oauth2.locals = req.oauth2.locals || {}; 27 | utils.merge(req.oauth2.locals, res.locals); 28 | } 29 | 30 | function immediated(err, allow, info, locals) { 31 | if (err) { return next(err); } 32 | if (allow) { 33 | req.oauth2.res = info || {}; 34 | req.oauth2.res.allow = true; 35 | if (locals) { 36 | req.oauth2.locals = req.oauth2.locals || {}; 37 | utils.merge(req.oauth2.locals, locals); 38 | } 39 | 40 | // proxy end() to delete the transaction 41 | var end = res.end; 42 | res.end = function(chunk, encoding) { 43 | if (server._txnStore.legacy == true) { 44 | server._txnStore.remove(options, req, req.oauth2.transactionID, function noop(){}); 45 | } else { 46 | server._txnStore.remove(req, req.oauth2.transactionID, function noop(){}); 47 | } 48 | 49 | res.end = end; 50 | res.end(chunk, encoding); 51 | }; 52 | req.oauth2._endProxied = true; 53 | 54 | function completing(cb) { 55 | if (!complete) { return cb(); } 56 | complete(req, req.oauth2, cb); 57 | } 58 | 59 | server._respond(req.oauth2, res, completing, function(err) { 60 | if (err) { return next(err); } 61 | return next(new AuthorizationError('Unsupported response type: ' + req.oauth2.req.type, 'unsupported_response_type')); 62 | }); 63 | } else { 64 | req.oauth2.info = info; 65 | if (locals) { 66 | req.oauth2.locals = req.oauth2.locals || {}; 67 | utils.merge(req.oauth2.locals, locals); 68 | } 69 | 70 | function updated(err, tid) { 71 | if (err) { return next(err); } 72 | req.oauth2.transactionID = tid; 73 | next(); 74 | } 75 | 76 | if (server._txnStore.legacy == true) { 77 | var txn = {}; 78 | txn.protocol = 'oauth2'; 79 | txn.client = req.oauth2.client; 80 | txn.redirectURI = req.oauth2.redirectURI; 81 | txn.req = req.oauth2.req; 82 | txn.info = info; 83 | 84 | server._txnStore.update(server, options, req, req.oauth2.transactionID, txn, updated); 85 | } else { 86 | server._txnStore.update(req, req.oauth2.transactionID, req.oauth2, updated); 87 | } 88 | } 89 | } 90 | 91 | try { 92 | var arity = immediate.length; 93 | if (arity == 7) { 94 | immediate(req.oauth2.client, req.oauth2.user, req.oauth2.req.scope, req.oauth2.req.type, req.oauth2.req, req.oauth2.locals, immediated); 95 | } else if (arity == 6) { 96 | immediate(req.oauth2.client, req.oauth2.user, req.oauth2.req.scope, req.oauth2.req.type, req.oauth2.req, immediated); 97 | } else if (arity == 5) { 98 | immediate(req.oauth2.client, req.oauth2.user, req.oauth2.req.scope, req.oauth2.req.type, immediated); 99 | } else if (arity == 4) { 100 | immediate(req.oauth2.client, req.oauth2.user, req.oauth2.req.scope, immediated); 101 | } else if (arity == 3) { 102 | immediate(req.oauth2.client, req.oauth2.user, immediated); 103 | } else { // arity == 2 104 | immediate(req.oauth2, immediated); 105 | } 106 | } catch (ex) { 107 | return next(ex); 108 | } 109 | }; 110 | }; 111 | -------------------------------------------------------------------------------- /lib/middleware/token.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | var TokenError = require('../errors/tokenerror'); 5 | 6 | 7 | /** 8 | * Exchanges authorization grants for access tokens. 9 | * 10 | * Obtaining authorization via OAuth 2.0 consists of a sequence of discrete 11 | * steps. First, the client requests authorization from the user (in this case 12 | * using an authorization server as an intermediary). The authorization server 13 | * conducts an approval dialog with the user to obtain permission. After access 14 | * has been allowed, a grant is issued to the client which can be exchanged for 15 | * an access token. 16 | * 17 | * This middleware is used to exchange a previously issued authorization grant 18 | * for an access token (a string denoting a specific scope, lifetime, and other 19 | * access attributes). 20 | * 21 | * The types of the grants that can be exchanged will depend on the types 22 | * supported by the server. An application can implement support for these 23 | * types as necessary, including taking advantage of bundled grant and exchange 24 | * middleware. 25 | * 26 | * Note that clients issued credentials must authenticate when when making 27 | * requests to the token endpoint. This is essential for enforcing the binding 28 | * of authorization codes and refresh tokens to the client they were issued to. 29 | * Some client deployments may be incapable of secure client authentication. 30 | * Applications are responsible for determining what level of exposure is 31 | * acceptable, and handling such clients and displaying notices as appropriate. 32 | * 33 | * Examples: 34 | * 35 | * app.post('/token', 36 | * passport.authenticate(['basic', 'oauth2-client-password'], { session: false }), 37 | * server.token(), 38 | * server.errorHandler()); 39 | * 40 | * References: 41 | * - [Token Endpoint](http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-3.2) 42 | * 43 | * @param {Server} server 44 | * @param {Object} options 45 | * @return {Function} 46 | * @api protected 47 | */ 48 | module.exports = function token(server, options) { 49 | options = options || {}; 50 | 51 | if (!server) { throw new TypeError('oauth2orize.token middleware requires a server argument'); } 52 | 53 | return function token(req, res, next) { 54 | var type = req.body.grant_type; 55 | 56 | server._exchange(type, req, res, function(err) { 57 | if (err) { return next(err); } 58 | return next(new TokenError('Unsupported grant type: ' + type, 'unsupported_grant_type')); 59 | }); 60 | }; 61 | }; 62 | -------------------------------------------------------------------------------- /lib/middleware/transactionLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | var AuthorizationError = require('../errors/authorizationerror') 5 | , BadRequestError = require('../errors/badrequesterror') 6 | , ForbiddenError = require('../errors/forbiddenerror'); 7 | 8 | 9 | /** 10 | * Loads an OAuth 2.0 authorization transaction from the session. 11 | * 12 | * This middleware is used to load a pending OAuth 2.0 transaction that is 13 | * serialized into the session. In most circumstances, this is transparently 14 | * done prior to processing a user's decision with `decision` middleware, and an 15 | * implementation shouldn't need to mount this middleware explicitly. 16 | * 17 | * Options: 18 | * 19 | * transactionField name of field that contains the transaction ID (default: 'transaction_id') 20 | * sessionKey key under which transactions are stored in the session (default: 'authorize') 21 | * 22 | * @param {Server} server 23 | * @param {Object} options 24 | * @return {Function} 25 | * @api protected 26 | */ 27 | module.exports = function(server, options) { 28 | options = options || {}; 29 | 30 | if (!server) { throw new TypeError('oauth2orize.transactionLoader middleware requires a server argument'); } 31 | 32 | return function transactionLoader(req, res, next) { 33 | if (req.oauth2) { return next(); } 34 | 35 | function loaded(err, txn) { 36 | if (err) { return next(err); } 37 | req.oauth2 = txn; 38 | next(); 39 | } 40 | 41 | if (server._txnStore.legacy == true) { 42 | server._txnStore.load(server, options, req, loaded); 43 | } else { 44 | server._txnStore.load(req, loaded); 45 | } 46 | }; 47 | }; 48 | -------------------------------------------------------------------------------- /lib/response/fragment.js: -------------------------------------------------------------------------------- 1 | var url = require('url') 2 | , qs = require('querystring') 3 | , AuthorizationError = require('../errors/authorizationerror'); 4 | 5 | /** 6 | * Authorization Response parameters are encoded in the fragment added to the redirect_uri when 7 | * redirecting back to the Client. 8 | **/ 9 | exports = module.exports = function (txn, res, params) { 10 | var parsed = url.parse(txn.redirectURI); 11 | parsed.hash = qs.stringify(params); 12 | 13 | var location = url.format(parsed); 14 | return res.redirect(location); 15 | }; 16 | 17 | 18 | exports.validate = function(txn) { 19 | if (!txn.redirectURI) { throw new AuthorizationError('Unable to issue redirect for OAuth 2.0 transaction', 'server_error'); } 20 | }; 21 | -------------------------------------------------------------------------------- /lib/response/query.js: -------------------------------------------------------------------------------- 1 | var url = require('url') 2 | , AuthorizationError = require('../errors/authorizationerror'); 3 | 4 | /** 5 | * Authorization Response parameters are encoded in the query string added to the redirect_uri when 6 | * redirecting back to the Client. 7 | **/ 8 | exports = module.exports = function (txn, res, params) { 9 | var parsed = url.parse(txn.redirectURI, true); 10 | delete parsed.search; 11 | Object.keys(params).forEach(function (k) { 12 | parsed.query[k] = params[k]; 13 | }); 14 | 15 | var location = url.format(parsed); 16 | return res.redirect(location); 17 | }; 18 | 19 | exports.validate = function(txn) { 20 | if (!txn.redirectURI) { throw new AuthorizationError('Unable to issue redirect for OAuth 2.0 transaction', 'server_error'); } 21 | }; 22 | -------------------------------------------------------------------------------- /lib/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | var SessionStore = require('./txn/session') 5 | , UnorderedList = require('./unorderedlist') 6 | , authorization = require('./middleware/authorization') 7 | , resume = require('./middleware/resume') 8 | , decision = require('./middleware/decision') 9 | , transactionLoader = require('./middleware/transactionLoader') 10 | , token = require('./middleware/token') 11 | , authorizationErrorHandler = require('./middleware/authorizationErrorHandler') 12 | , errorHandler = require('./middleware/errorHandler') 13 | , utils = require('./utils') 14 | , debug = require('debug')('oauth2orize'); 15 | 16 | 17 | /** 18 | * `Server` constructor. 19 | * 20 | * @api public 21 | */ 22 | function Server(options) { 23 | options = options || {}; 24 | this._reqParsers = []; 25 | this._resHandlers = []; 26 | this._errHandlers = []; 27 | this._exchanges = []; 28 | 29 | this._serializers = []; 30 | this._deserializers = []; 31 | this._txnStore = options.store || new SessionStore(); 32 | } 33 | 34 | /** 35 | * Register authorization grant middleware. 36 | * 37 | * OAuth 2.0 defines an authorization framework, in which authorization grants 38 | * can be of a variety of types. Initiating and responding to an OAuth 2.0 39 | * authorization transaction is implemented by grant middleware, and the server 40 | * registers the middleware it wishes to support. 41 | * 42 | * Examples: 43 | * 44 | * server.grant(oauth2orize.grant.code()); 45 | * 46 | * server.grant('*', function(req) { 47 | * return { host: req.headers['host'] } 48 | * }); 49 | * 50 | * server.grant('foo', function(req) { 51 | * return { foo: req.query['foo'] } 52 | * }); 53 | * 54 | * @param {String|Object} type 55 | * @param {String} phase 56 | * @param {Function} fn 57 | * @return {Server} for chaining 58 | * @api public 59 | */ 60 | Server.prototype.grant = function(type, phase, fn) { 61 | if (typeof type == 'object') { 62 | // sig: grant(mod) 63 | var mod = type; 64 | if (mod.request) { this.grant(mod.name, 'request', mod.request); } 65 | if (mod.response) { this.grant(mod.name, 'response', mod.response); } 66 | if (mod.error) { this.grant(mod.name, 'error', mod.error); } 67 | return this; 68 | } 69 | if (typeof phase == 'object') { 70 | // sig: grant(type, mod) 71 | var mod = phase; 72 | if (mod.request) { this.grant(type, 'request', mod.request); } 73 | if (mod.response) { this.grant(type, 'response', mod.response); } 74 | if (mod.error) { this.grant(type, 'error', mod.error); } 75 | return this; 76 | } 77 | 78 | if (typeof phase == 'function') { 79 | // sig: grant(type, fn) 80 | fn = phase; 81 | phase = 'request'; 82 | } 83 | if (type === '*') { type = null; } 84 | if (type) { type = new UnorderedList(type); } 85 | 86 | if (phase == 'request') { 87 | debug('register request parser %s %s', type || '*', fn.name || 'anonymous'); 88 | this._reqParsers.push({ type: type, handle: fn }); 89 | } else if (phase == 'response') { 90 | debug('register response handler %s %s', type || '*', fn.name || 'anonymous'); 91 | this._resHandlers.push({ type: type, handle: fn }); 92 | } else if (phase == 'error') { 93 | debug('register error handler %s %s', type || '*', fn.name || 'anonymous'); 94 | this._errHandlers.push({ type: type, handle: fn }); 95 | } 96 | return this; 97 | }; 98 | 99 | /** 100 | * Register token exchange middleware. 101 | * 102 | * OAuth 2.0 defines an authorization framework, in which authorization grants 103 | * can be of a variety of types. Exchanging of these types for access tokens is 104 | * implemented by exchange middleware, and the server registers the middleware 105 | * it wishes to support. 106 | * 107 | * Examples: 108 | * 109 | * server.exchange(oauth2orize.exchange.authorizationCode(function() { 110 | * ... 111 | * })); 112 | * 113 | * @param {String|Function} type 114 | * @param {Function} fn 115 | * @return {Server} for chaining 116 | * @api public 117 | */ 118 | Server.prototype.exchange = function(type, fn) { 119 | if (typeof type == 'function') { 120 | fn = type; 121 | type = fn.name; 122 | } 123 | if (type === '*') { type = null; } 124 | 125 | debug('register exchanger %s %s', type || '*', fn.name || 'anonymous'); 126 | this._exchanges.push({ type: type, handle: fn }); 127 | return this; 128 | }; 129 | 130 | /** 131 | * Parses requests to obtain authorization. 132 | * 133 | * @api public 134 | */ 135 | Server.prototype.authorize = 136 | Server.prototype.authorization = function(options, validate, immediate, complete) { 137 | return authorization(this, options, validate, immediate, complete); 138 | }; 139 | 140 | Server.prototype.resume = function(options, immediate, complete) { 141 | var loader; 142 | if (typeof options == 'function' && typeof immediate == 'function' && typeof complete == 'function') { 143 | options = { loadTransaction: options }; 144 | } 145 | 146 | if (options && options.loadTransaction === false) { 147 | return resume(this, options, immediate, complete); 148 | } 149 | if (options && typeof options.loadTransaction === 'function') { 150 | loader = options.loadTransaction; 151 | } else { 152 | loader = transactionLoader(this, options); 153 | } 154 | return [loader, resume(this, options, immediate, complete)]; 155 | }; 156 | 157 | /** 158 | * Handle a user's response to an authorization dialog. 159 | * 160 | * @api public 161 | */ 162 | Server.prototype.decision = function(options, parse, complete) { 163 | if (options && options.loadTransaction === false) { 164 | return decision(this, options, parse, complete); 165 | } 166 | return [transactionLoader(this, options), decision(this, options, parse, complete)]; 167 | }; 168 | 169 | Server.prototype.authorizeError = 170 | Server.prototype.authorizationError = 171 | Server.prototype.authorizationErrorHandler = function(options) { 172 | var loader = transactionLoader(this, options); 173 | 174 | return [ 175 | function transactionLoaderErrorWrapper(err, req, res, next) { 176 | loader(req, res, function(ierr) { 177 | return next(err); 178 | }); 179 | }, 180 | authorizationErrorHandler(this, options) 181 | ]; 182 | }; 183 | 184 | /** 185 | * Handle requests to exchange an authorization grant for an access token. 186 | * 187 | * @api public 188 | */ 189 | Server.prototype.token = function(options) { 190 | return token(this, options); 191 | }; 192 | 193 | /** 194 | * Respond to errors encountered in OAuth 2.0 endpoints. 195 | * 196 | * @api public 197 | */ 198 | Server.prototype.errorHandler = function(options) { 199 | return errorHandler(options); 200 | }; 201 | 202 | /** 203 | * Registers a function used to serialize client objects into the session. 204 | * 205 | * Examples: 206 | * 207 | * server.serializeClient(function(client, done) { 208 | * done(null, client.id); 209 | * }); 210 | * 211 | * @api public 212 | */ 213 | Server.prototype.serializeClient = function(fn, done) { 214 | if (typeof fn === 'function') { 215 | return this._serializers.push(fn); 216 | } 217 | 218 | // private implementation that traverses the chain of serializers, attempting 219 | // to serialize a client 220 | var client = fn; 221 | 222 | var stack = this._serializers; 223 | (function pass(i, err, obj) { 224 | // serializers use 'pass' as an error to skip processing 225 | if ('pass' === err) { err = undefined; } 226 | // an error or serialized object was obtained, done 227 | if (err || obj) { return done(err, obj); } 228 | 229 | var layer = stack[i]; 230 | if (!layer) { 231 | return done(new Error('Failed to serialize client. Register serialization function using serializeClient().')); 232 | } 233 | 234 | try { 235 | layer(client, function(e, o) { pass(i + 1, e, o); } ); 236 | } catch (ex) { 237 | return done(ex); 238 | } 239 | })(0); 240 | }; 241 | 242 | /** 243 | * Registers a function used to deserialize client objects out of the session. 244 | * 245 | * Examples: 246 | * 247 | * server.deserializeClient(function(id, done) { 248 | * Client.findById(id, function (err, client) { 249 | * done(err, client); 250 | * }); 251 | * }); 252 | * 253 | * @api public 254 | */ 255 | Server.prototype.deserializeClient = function(fn, done) { 256 | if (typeof fn === 'function') { 257 | return this._deserializers.push(fn); 258 | } 259 | 260 | // private implementation that traverses the chain of deserializers, 261 | // attempting to deserialize a client 262 | var obj = fn; 263 | 264 | var stack = this._deserializers; 265 | (function pass(i, err, client) { 266 | // deserializers use 'pass' as an error to skip processing 267 | if ('pass' === err) { err = undefined; } 268 | // an error or deserialized client was obtained, done 269 | if (err || client) { return done(err, client); } 270 | // a valid client existed when establishing the session, but that client has 271 | // since been deauthorized 272 | if (client === null || client === false) { return done(null, false); } 273 | 274 | var layer = stack[i]; 275 | if (!layer) { 276 | return done(new Error('Failed to deserialize client. Register deserialization function using deserializeClient().')); 277 | } 278 | 279 | try { 280 | layer(obj, function(e, c) { pass(i + 1, e, c); } ); 281 | } catch (ex) { 282 | return done(ex); 283 | } 284 | })(0); 285 | }; 286 | 287 | 288 | /** 289 | * Parse authorization request into transaction using registered grant middleware. 290 | * 291 | * @param {String} type 292 | * @param {http.ServerRequest} req 293 | * @param {Function} cb 294 | * @api private 295 | */ 296 | Server.prototype._parse = function(type, req, cb) { 297 | var ultype = new UnorderedList(type) 298 | , stack = this._reqParsers 299 | , areq = {}; 300 | 301 | if (type) { areq.type = type; } 302 | 303 | (function pass(i) { 304 | var layer = stack[i]; 305 | if (!layer) { return cb(null, areq); } 306 | 307 | try { 308 | debug('parse:%s', layer.handle.name || 'anonymous'); 309 | if (layer.type === null || layer.type.equalTo(ultype)) { 310 | var arity = layer.handle.length; 311 | if (arity == 1) { // sync 312 | var o = layer.handle(req); 313 | utils.merge(areq, o); 314 | pass(i + 1); 315 | } else { // async 316 | layer.handle(req, function(err, o) { 317 | if (err) { return cb(err); } 318 | utils.merge(areq, o); 319 | pass(i + 1); 320 | }); 321 | } 322 | } else { 323 | pass(i + 1); 324 | } 325 | } catch (ex) { 326 | return cb(ex); 327 | } 328 | })(0); 329 | }; 330 | 331 | /** 332 | * Respond to authorization transaction using registered grant middleware. 333 | * 334 | * @param {Object} txn 335 | * @param {http.ServerResponse} res 336 | * @param {Function} cb 337 | * @api private 338 | */ 339 | Server.prototype._respond = function(txn, res, complete, cb) { 340 | if (cb === undefined) { 341 | cb = complete; 342 | complete = undefined; 343 | } 344 | complete = complete || function(cb) { cb(); } 345 | 346 | var ultype = new UnorderedList(txn.req.type) 347 | , stack = this._resHandlers 348 | , idx = 0; 349 | 350 | function next(err) { 351 | if (err) { return cb(err); } 352 | 353 | var layer = stack[idx++]; 354 | if (!layer) { return cb(); } 355 | 356 | try { 357 | debug('respond:%s', layer.handle.name || 'anonymous'); 358 | if (layer.type === null || layer.type.equalTo(ultype)) { 359 | var arity = layer.handle.length; 360 | if (arity == 4) { 361 | layer.handle(txn, res, complete, next); 362 | } else { 363 | layer.handle(txn, res, next); 364 | } 365 | } else { 366 | next(); 367 | } 368 | } catch (ex) { 369 | return cb(ex); 370 | } 371 | } 372 | next(); 373 | }; 374 | 375 | Server.prototype._respondError = function(err, txn, res, cb) { 376 | var ultype = new UnorderedList(txn.req.type) 377 | , stack = this._errHandlers 378 | , idx = 0; 379 | 380 | function next(err) { 381 | var layer = stack[idx++]; 382 | if (!layer) { return cb(err); } 383 | 384 | try { 385 | debug('error:%s', layer.handle.name || 'anonymous'); 386 | if (layer.type === null || layer.type.equalTo(ultype)) { 387 | layer.handle(err, txn, res, next); 388 | } else { 389 | next(err); 390 | } 391 | } catch (ex) { 392 | return cb(ex); 393 | } 394 | } 395 | next(err); 396 | } 397 | 398 | /** 399 | * Process token request using registered exchange middleware. 400 | * 401 | * @param {String} type 402 | * @param {http.ServerRequest} req 403 | * @param {http.ServerResponse} res 404 | * @param {Function} cb 405 | * @api private 406 | */ 407 | Server.prototype._exchange = function(type, req, res, cb) { 408 | var stack = this._exchanges 409 | , idx = 0; 410 | 411 | function next(err) { 412 | if (err) { return cb(err); } 413 | 414 | var layer = stack[idx++]; 415 | if (!layer) { return cb(); } 416 | 417 | try { 418 | debug('exchange:%s', layer.handle.name || 'anonymous'); 419 | if (layer.type === null || layer.type === type) { 420 | layer.handle(req, res, next); 421 | } else { 422 | next(); 423 | } 424 | } catch (ex) { 425 | return cb(ex); 426 | } 427 | } 428 | next(); 429 | }; 430 | 431 | 432 | /** 433 | * Expose `Server`. 434 | */ 435 | exports = module.exports = Server; 436 | -------------------------------------------------------------------------------- /lib/txn/session.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | var utils = require('../utils') 5 | , AuthorizationError = require('../errors/authorizationerror') 6 | , BadRequestError = require('../errors/badrequesterror') 7 | , ForbiddenError = require('../errors/forbiddenerror'); 8 | 9 | 10 | function SessionStore() { 11 | this.legacy = true; 12 | } 13 | 14 | SessionStore.prototype.load = function(server, options, req, cb) { 15 | var field = options.transactionField || 'transaction_id' 16 | , key = options.sessionKey || 'authorize'; 17 | 18 | if (!req.session) { return cb(new Error('OAuth2orize requires session support. Did you forget app.use(express.session(...))?')); } 19 | if (!req.session[key]) { return cb(new ForbiddenError('Unable to load OAuth 2.0 transactions from session')); } 20 | 21 | var query = req.query || {} 22 | , body = req.body || {} 23 | , tid = query[field] || body[field]; 24 | 25 | if (!tid) { return cb(new BadRequestError('Missing required parameter: ' + field)); } 26 | var txn = req.session[key][tid]; 27 | if (!txn) { return cb(new ForbiddenError('Unable to load OAuth 2.0 transaction: ' + tid)); } 28 | 29 | var self = this; 30 | server.deserializeClient(txn.client, function(err, client) { 31 | if (err) { return cb(err); } 32 | if (!client) { 33 | // At the time the request was initiated, the client was validated. 34 | // Since then, however, it has been invalidated. The transaction will 35 | // be invalidated and no response will be sent to the client. 36 | self.remove(options, req, tid, function(err) { 37 | if (err) { return cb(err); } 38 | return cb(new AuthorizationError('Unauthorized client', 'unauthorized_client')); 39 | }); 40 | return; 41 | } 42 | 43 | txn.transactionID = tid; 44 | txn.client = client; 45 | cb(null, txn); 46 | }); 47 | } 48 | 49 | SessionStore.prototype.store = function(server, options, req, txn, cb) { 50 | var lenTxnID = options.idLength || 8 51 | , key = options.sessionKey || 'authorize'; 52 | 53 | if (!req.session) { return cb(new Error('OAuth2orize requires session support. Did you forget app.use(express.session(...))?')); } 54 | 55 | server.serializeClient(txn.client, function(err, obj) { 56 | if (err) { return cb(err); } 57 | 58 | var tid = utils.uid(lenTxnID); 59 | txn.client = obj; 60 | 61 | // store transaction in session 62 | var txns = req.session[key] = req.session[key] || {}; 63 | txns[tid] = txn; 64 | 65 | cb(null, tid); 66 | }); 67 | } 68 | 69 | SessionStore.prototype.update = function(server, options, req, tid, txn, cb) { 70 | var key = options.sessionKey || 'authorize'; 71 | 72 | server.serializeClient(txn.client, function(err, obj) { 73 | if (err) { return cb(err); } 74 | 75 | txn.client = obj; 76 | 77 | // store transaction in session 78 | var txns = req.session[key] = req.session[key] || {}; 79 | txns[tid] = txn; 80 | 81 | cb(null, tid); 82 | }); 83 | } 84 | 85 | SessionStore.prototype.remove = function(options, req, tid, cb) { 86 | var key = options.sessionKey || 'authorize'; 87 | 88 | if (!req.session) { return cb(new Error('OAuth2orize requires session support. Did you forget app.use(express.session(...))?')); } 89 | 90 | if (req.session[key]) { 91 | delete req.session[key][tid]; 92 | } 93 | 94 | cb(); 95 | } 96 | 97 | 98 | module.exports = SessionStore; 99 | -------------------------------------------------------------------------------- /lib/unorderedlist.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `UnorderedList` constructor. 3 | * 4 | * @api public 5 | */ 6 | function UnorderedList(items) { 7 | if (typeof items == 'string') { 8 | items = items.split(' '); 9 | } 10 | this._items = items || []; 11 | this.__defineGetter__('length', this._length); 12 | } 13 | 14 | /** 15 | * Check if list is equal to `other` list. 16 | * 17 | * @param {UnorderedList} other 18 | * @return {Boolean} 19 | * @api public 20 | */ 21 | UnorderedList.prototype.equalTo = function(other) { 22 | if (!(other instanceof UnorderedList)) { 23 | other = new UnorderedList(other); 24 | } 25 | 26 | if (this.length != other.length) { return false; } 27 | for (var i = 0, len = this._items.length; i < len; i++) { 28 | var item = this._items[i]; 29 | if (other._items.indexOf(item) == -1) { 30 | return false; 31 | } 32 | } 33 | return true; 34 | }; 35 | 36 | /** 37 | * Check if list contains `val` 38 | * 39 | * @param {String} val 40 | * @return {Boolean} 41 | * @api public 42 | */ 43 | UnorderedList.prototype.contains = function(val) { 44 | return this._items.indexOf(val) != -1; 45 | }; 46 | 47 | /** 48 | * Check if list contains any element in `arr` 49 | * 50 | * @param {Array} arr 51 | * @return {Boolean} 52 | * @api public 53 | */ 54 | UnorderedList.prototype.containsAny = function(arr) { 55 | for (var i = 0, len = arr.length; i < len; i++) { 56 | if (this._items.indexOf(arr[i]) != -1) { return true; } 57 | } 58 | return false; 59 | }; 60 | 61 | /** 62 | * String representation of list. 63 | * 64 | * @return {String} 65 | * @api private 66 | */ 67 | UnorderedList.prototype.toString = function() { 68 | return this._items.join(' '); 69 | }; 70 | 71 | /** 72 | * Length of list. 73 | * 74 | * @return {Number} 75 | * @api private 76 | */ 77 | UnorderedList.prototype._length = function() { 78 | return this._items.length; 79 | }; 80 | 81 | /** 82 | * Expose `UnorderedList`. 83 | */ 84 | module.exports = UnorderedList; 85 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | exports.merge = require('utils-merge'); 2 | exports.uid = require('uid2'); 3 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oauth2orize", 3 | "version": "1.12.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "assertion-error": { 8 | "version": "1.0.0", 9 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.0.tgz", 10 | "integrity": "sha1-x/hUOP3UZrx8oWq5DIFRN5el0js=", 11 | "dev": true 12 | }, 13 | "chai": { 14 | "version": "2.3.0", 15 | "resolved": "https://registry.npmjs.org/chai/-/chai-2.3.0.tgz", 16 | "integrity": "sha1-ii9qNHSNqAEJD9cyh7Kqc5pOkJo=", 17 | "dev": true, 18 | "requires": { 19 | "assertion-error": "1.0.0", 20 | "deep-eql": "0.1.3" 21 | } 22 | }, 23 | "chai-connect-middleware": { 24 | "version": "0.3.1", 25 | "resolved": "https://registry.npmjs.org/chai-connect-middleware/-/chai-connect-middleware-0.3.1.tgz", 26 | "integrity": "sha1-6qGF6ZKhAtAyvW4ngAEqjmQ1hqg=", 27 | "dev": true 28 | }, 29 | "chai-oauth2orize-grant": { 30 | "version": "0.3.0", 31 | "resolved": "https://registry.npmjs.org/chai-oauth2orize-grant/-/chai-oauth2orize-grant-0.3.0.tgz", 32 | "integrity": "sha1-LUfkLA7o0ZsvwuNXgFYNhSmctDU=", 33 | "dev": true 34 | }, 35 | "commander": { 36 | "version": "2.3.0", 37 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz", 38 | "integrity": "sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM=", 39 | "dev": true 40 | }, 41 | "debug": { 42 | "version": "2.6.9", 43 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 44 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 45 | "requires": { 46 | "ms": "2.0.0" 47 | } 48 | }, 49 | "deep-eql": { 50 | "version": "0.1.3", 51 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", 52 | "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", 53 | "dev": true, 54 | "requires": { 55 | "type-detect": "0.1.1" 56 | } 57 | }, 58 | "diff": { 59 | "version": "1.4.0", 60 | "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", 61 | "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", 62 | "dev": true 63 | }, 64 | "escape-string-regexp": { 65 | "version": "1.0.2", 66 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz", 67 | "integrity": "sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE=", 68 | "dev": true 69 | }, 70 | "glob": { 71 | "version": "3.2.11", 72 | "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", 73 | "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", 74 | "dev": true, 75 | "requires": { 76 | "inherits": "2", 77 | "minimatch": "0.3" 78 | } 79 | }, 80 | "growl": { 81 | "version": "1.9.2", 82 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", 83 | "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", 84 | "dev": true 85 | }, 86 | "inherits": { 87 | "version": "2.0.4", 88 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 89 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 90 | "dev": true 91 | }, 92 | "jade": { 93 | "version": "0.26.3", 94 | "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", 95 | "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", 96 | "dev": true, 97 | "requires": { 98 | "commander": "0.6.1", 99 | "mkdirp": "0.3.0" 100 | }, 101 | "dependencies": { 102 | "commander": { 103 | "version": "0.6.1", 104 | "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", 105 | "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=", 106 | "dev": true 107 | }, 108 | "mkdirp": { 109 | "version": "0.3.0", 110 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", 111 | "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=", 112 | "dev": true 113 | } 114 | } 115 | }, 116 | "lru-cache": { 117 | "version": "2.7.3", 118 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", 119 | "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", 120 | "dev": true 121 | }, 122 | "make-node": { 123 | "version": "0.3.5", 124 | "resolved": "https://registry.npmjs.org/make-node/-/make-node-0.3.5.tgz", 125 | "integrity": "sha1-LTVN240+zfWg1btMrbuqRGHK3jo=", 126 | "dev": true 127 | }, 128 | "minimatch": { 129 | "version": "0.3.0", 130 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", 131 | "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", 132 | "dev": true, 133 | "requires": { 134 | "lru-cache": "2", 135 | "sigmund": "~1.0.0" 136 | } 137 | }, 138 | "minimist": { 139 | "version": "0.0.8", 140 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 141 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 142 | "dev": true 143 | }, 144 | "mkdirp": { 145 | "version": "0.5.1", 146 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 147 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 148 | "dev": true, 149 | "requires": { 150 | "minimist": "0.0.8" 151 | } 152 | }, 153 | "mocha": { 154 | "version": "2.5.3", 155 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-2.5.3.tgz", 156 | "integrity": "sha1-FhvlvetJZ3HrmzV0UFC2IrWu/Fg=", 157 | "dev": true, 158 | "requires": { 159 | "commander": "2.3.0", 160 | "debug": "2.2.0", 161 | "diff": "1.4.0", 162 | "escape-string-regexp": "1.0.2", 163 | "glob": "3.2.11", 164 | "growl": "1.9.2", 165 | "jade": "0.26.3", 166 | "mkdirp": "0.5.1", 167 | "supports-color": "1.2.0", 168 | "to-iso-string": "0.0.2" 169 | }, 170 | "dependencies": { 171 | "debug": { 172 | "version": "2.2.0", 173 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", 174 | "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", 175 | "dev": true, 176 | "requires": { 177 | "ms": "0.7.1" 178 | } 179 | }, 180 | "ms": { 181 | "version": "0.7.1", 182 | "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", 183 | "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", 184 | "dev": true 185 | } 186 | } 187 | }, 188 | "ms": { 189 | "version": "2.0.0", 190 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 191 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 192 | }, 193 | "sigmund": { 194 | "version": "1.0.1", 195 | "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", 196 | "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", 197 | "dev": true 198 | }, 199 | "supports-color": { 200 | "version": "1.2.0", 201 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.2.0.tgz", 202 | "integrity": "sha1-/x7R5hFp0Gs88tWI4YixjYhH4X4=", 203 | "dev": true 204 | }, 205 | "to-iso-string": { 206 | "version": "0.0.2", 207 | "resolved": "https://registry.npmjs.org/to-iso-string/-/to-iso-string-0.0.2.tgz", 208 | "integrity": "sha1-TcGeZk38y+Jb2NtQiwDG2hWCVdE=", 209 | "dev": true 210 | }, 211 | "type-detect": { 212 | "version": "0.1.1", 213 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", 214 | "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", 215 | "dev": true 216 | }, 217 | "uid2": { 218 | "version": "0.0.4", 219 | "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz", 220 | "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==" 221 | }, 222 | "utils-merge": { 223 | "version": "1.0.1", 224 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 225 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 226 | } 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oauth2orize", 3 | "version": "1.12.0", 4 | "description": "OAuth 2.0 authorization server toolkit for Node.js.", 5 | "keywords": [ 6 | "oauth", 7 | "oauth2", 8 | "auth", 9 | "authz", 10 | "authorization", 11 | "connect", 12 | "express", 13 | "passport", 14 | "middleware" 15 | ], 16 | "author": { 17 | "name": "Jared Hanson", 18 | "email": "jaredhanson@gmail.com", 19 | "url": "https://www.jaredhanson.me/" 20 | }, 21 | "contributors": [ 22 | { 23 | "name": "Samuel Judson", 24 | "url": "https://www.sjudson.com/" 25 | } 26 | ], 27 | "repository": { 28 | "type": "git", 29 | "url": "git://github.com/jaredhanson/oauth2orize.git" 30 | }, 31 | "bugs": { 32 | "url": "https://github.com/jaredhanson/oauth2orize/issues" 33 | }, 34 | "funding": { 35 | "type": "github", 36 | "url": "https://github.com/sponsors/jaredhanson" 37 | }, 38 | "license": "MIT", 39 | "licenses": [ 40 | { 41 | "type": "MIT", 42 | "url": "https://opensource.org/licenses/MIT" 43 | } 44 | ], 45 | "main": "./lib", 46 | "dependencies": { 47 | "uid2": "0.0.x", 48 | "utils-merge": "1.x.x", 49 | "debug": "2.x.x" 50 | }, 51 | "devDependencies": { 52 | "make-node": "0.3.x", 53 | "mocha": "2.x.x", 54 | "chai": "2.x.x", 55 | "chai-connect-middleware": "0.3.x", 56 | "chai-oauth2orize-grant": "0.3.x" 57 | }, 58 | "engines": { 59 | "node": ">= 0.4.0" 60 | }, 61 | "scripts": { 62 | "test": "node_modules/.bin/mocha --reporter spec --require test/bootstrap/node test/*.test.js test/**/*.test.js" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /test/bootstrap/node.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | 3 | chai.use(require('chai-connect-middleware')); 4 | chai.use(require('chai-oauth2orize-grant')); 5 | 6 | global.expect = chai.expect; 7 | -------------------------------------------------------------------------------- /test/errors/authorizationerror.test.js: -------------------------------------------------------------------------------- 1 | var OAuth2Error = require('../../lib/errors/oauth2error') 2 | , AuthorizationError = require('../../lib/errors/authorizationerror'); 3 | 4 | 5 | describe('AuthorizationError', function() { 6 | 7 | describe('constructed without a message', function() { 8 | var err = new AuthorizationError(); 9 | 10 | it('should have default properties', function() { 11 | expect(err.message).to.be.undefined; 12 | expect(err.code).to.equal('server_error'); 13 | expect(err.uri).to.be.undefined; 14 | expect(err.status).to.equal(500); 15 | }); 16 | 17 | it('should format correctly', function() { 18 | //expect(err.toString()).to.equal('AuthorizationError'); 19 | expect(err.toString().indexOf('AuthorizationError')).to.equal(0); 20 | }); 21 | 22 | it('should inherits from OAuth2Error and Error', function() { 23 | expect(err).to.be.instanceof(OAuth2Error); 24 | expect(err).to.be.instanceof(Error); 25 | }); 26 | }); 27 | 28 | describe('constructed with a message', function() { 29 | var err = new AuthorizationError('Invalid return URI'); 30 | 31 | it('should have default properties', function() { 32 | expect(err.message).to.equal('Invalid return URI'); 33 | expect(err.code).to.equal('server_error'); 34 | expect(err.uri).to.be.undefined; 35 | expect(err.status).to.equal(500); 36 | }); 37 | 38 | it('should format correctly', function() { 39 | expect(err.toString()).to.equal('AuthorizationError: Invalid return URI'); 40 | }); 41 | }); 42 | 43 | describe('constructed with a message and invalid_request code', function() { 44 | var err = new AuthorizationError('Invalid request', 'invalid_request'); 45 | 46 | it('should have default properties', function() { 47 | expect(err.message).to.equal('Invalid request'); 48 | expect(err.code).to.equal('invalid_request'); 49 | expect(err.uri).to.be.undefined; 50 | expect(err.status).to.equal(400); 51 | }); 52 | }); 53 | 54 | describe('constructed with a message and unauthorized_client code', function() { 55 | var err = new AuthorizationError('Unauthorized client', 'unauthorized_client'); 56 | 57 | it('should have default properties', function() { 58 | expect(err.message).to.equal('Unauthorized client'); 59 | expect(err.code).to.equal('unauthorized_client'); 60 | expect(err.uri).to.be.undefined; 61 | expect(err.status).to.equal(403); 62 | }); 63 | }); 64 | 65 | describe('constructed with a message and access_denied code', function() { 66 | var err = new AuthorizationError('Access denied', 'access_denied'); 67 | 68 | it('should have default properties', function() { 69 | expect(err.message).to.equal('Access denied'); 70 | expect(err.code).to.equal('access_denied'); 71 | expect(err.uri).to.be.undefined; 72 | expect(err.status).to.equal(403); 73 | }); 74 | }); 75 | 76 | describe('constructed with a message and unsupported_response_type code', function() { 77 | var err = new AuthorizationError('Unsupported response type', 'unsupported_response_type'); 78 | 79 | it('should have default properties', function() { 80 | expect(err.message).to.equal('Unsupported response type'); 81 | expect(err.code).to.equal('unsupported_response_type'); 82 | expect(err.uri).to.be.undefined; 83 | expect(err.status).to.equal(501); 84 | }); 85 | }); 86 | 87 | describe('constructed with a message and invalid_scope code', function() { 88 | var err = new AuthorizationError('Invalid scope', 'invalid_scope'); 89 | 90 | it('should have default properties', function() { 91 | expect(err.message).to.equal('Invalid scope'); 92 | expect(err.code).to.equal('invalid_scope'); 93 | expect(err.uri).to.be.undefined; 94 | expect(err.status).to.equal(400); 95 | }); 96 | }); 97 | 98 | describe('constructed with a message and temporarily_unavailable code', function() { 99 | var err = new AuthorizationError('Temporarily unavailable', 'temporarily_unavailable'); 100 | 101 | it('should have default properties', function() { 102 | expect(err.message).to.equal('Temporarily unavailable'); 103 | expect(err.code).to.equal('temporarily_unavailable'); 104 | expect(err.uri).to.be.undefined; 105 | expect(err.status).to.equal(503); 106 | }); 107 | }); 108 | 109 | describe('constructed with a message, code, uri and status', function() { 110 | var err = new AuthorizationError('Payment required', 'payment_required', 'http://www.example.com/oauth/help', 402); 111 | 112 | it('should have default properties', function() { 113 | expect(err.message).to.equal('Payment required'); 114 | expect(err.code).to.equal('payment_required'); 115 | expect(err.uri).to.equal('http://www.example.com/oauth/help'); 116 | expect(err.status).to.equal(402); 117 | }); 118 | 119 | it('should format correctly', function() { 120 | expect(err.toString()).to.equal('AuthorizationError: Payment required'); 121 | }); 122 | }); 123 | 124 | }); 125 | -------------------------------------------------------------------------------- /test/errors/badrequesterror.test.js: -------------------------------------------------------------------------------- 1 | var BadRequestError = require('../../lib/errors/badrequesterror'); 2 | 3 | 4 | describe('BadRequestError', function() { 5 | 6 | describe('constructed without a message', function() { 7 | var err = new BadRequestError(); 8 | 9 | it('should have default properties', function() { 10 | expect(err.message).to.be.undefined; 11 | }); 12 | 13 | it('should format correctly', function() { 14 | //expect(err.toString()).to.equal('BadRequestError'); 15 | expect(err.toString().indexOf('BadRequestError')).to.equal(0); 16 | }); 17 | 18 | it('should have status', function() { 19 | expect(err.status).to.equal(400); 20 | }); 21 | 22 | it('should inherits from Error', function() { 23 | expect(err).to.be.instanceof(Error); 24 | }); 25 | }); 26 | 27 | describe('constructed with a message', function() { 28 | var err = new BadRequestError('Bad request'); 29 | 30 | it('should have default properties', function() { 31 | expect(err.message).to.equal('Bad request'); 32 | }); 33 | 34 | it('should format correctly', function() { 35 | expect(err.toString()).to.equal('BadRequestError: Bad request'); 36 | }); 37 | 38 | it('should have status', function() { 39 | expect(err.status).to.equal(400); 40 | }); 41 | }); 42 | 43 | }); 44 | -------------------------------------------------------------------------------- /test/errors/forbiddenerror.test.js: -------------------------------------------------------------------------------- 1 | var ForbiddenError = require('../../lib/errors/forbiddenerror'); 2 | 3 | 4 | describe('ForbiddenError', function() { 5 | 6 | describe('constructed without a message', function() { 7 | var err = new ForbiddenError(); 8 | 9 | it('should have default properties', function() { 10 | expect(err.message).to.be.undefined; 11 | }); 12 | 13 | it('should format correctly', function() { 14 | //expect(err.toString()).to.equal('ForbiddenError'); 15 | expect(err.toString().indexOf('ForbiddenError')).to.equal(0); 16 | }); 17 | 18 | it('should have status', function() { 19 | expect(err.status).to.equal(403); 20 | }); 21 | 22 | it('should inherits from Error', function() { 23 | expect(err).to.be.instanceof(Error); 24 | }); 25 | }); 26 | 27 | describe('constructed with a message', function() { 28 | var err = new ForbiddenError('Forbidden'); 29 | 30 | it('should have default properties', function() { 31 | expect(err.message).to.equal('Forbidden'); 32 | }); 33 | 34 | it('should format correctly', function() { 35 | expect(err.toString()).to.equal('ForbiddenError: Forbidden'); 36 | }); 37 | 38 | it('should have status', function() { 39 | expect(err.status).to.equal(403); 40 | }); 41 | }); 42 | 43 | }); 44 | -------------------------------------------------------------------------------- /test/errors/tokenerror.test.js: -------------------------------------------------------------------------------- 1 | var OAuth2Error = require('../../lib/errors/oauth2error') 2 | , TokenError = require('../../lib/errors/tokenerror'); 3 | 4 | 5 | describe('TokenError', function() { 6 | 7 | describe('constructed without a message', function() { 8 | var err = new TokenError(); 9 | 10 | it('should have default properties', function() { 11 | expect(err.message).to.be.undefined; 12 | expect(err.code).to.equal('server_error'); 13 | expect(err.uri).to.be.undefined; 14 | expect(err.status).to.equal(500); 15 | }); 16 | 17 | it('should format correctly', function() { 18 | //expect(err.toString()).to.equal('AuthorizationError'); 19 | expect(err.toString().indexOf('TokenError')).to.equal(0); 20 | }); 21 | 22 | it('should inherits from OAuth2Error and Error', function() { 23 | expect(err).to.be.instanceof(OAuth2Error); 24 | expect(err).to.be.instanceof(Error); 25 | }); 26 | }); 27 | 28 | describe('constructed with a message', function() { 29 | var err = new TokenError('Invalid return URI'); 30 | 31 | it('should have default properties', function() { 32 | expect(err.message).to.equal('Invalid return URI'); 33 | expect(err.code).to.equal('server_error'); 34 | expect(err.uri).to.be.undefined; 35 | expect(err.status).to.equal(500); 36 | }); 37 | 38 | it('should format correctly', function() { 39 | expect(err.toString()).to.equal('TokenError: Invalid return URI'); 40 | }); 41 | }); 42 | 43 | describe('constructed with a message and invalid_request code', function() { 44 | var err = new TokenError('Invalid request', 'invalid_request'); 45 | 46 | it('should have default properties', function() { 47 | expect(err.message).to.equal('Invalid request'); 48 | expect(err.code).to.equal('invalid_request'); 49 | expect(err.uri).to.be.undefined; 50 | expect(err.status).to.equal(400); 51 | }); 52 | }); 53 | 54 | describe('constructed with a message and invalid_client code', function() { 55 | var err = new TokenError('Invalid client', 'invalid_client'); 56 | 57 | it('should have default properties', function() { 58 | expect(err.message).to.equal('Invalid client'); 59 | expect(err.code).to.equal('invalid_client'); 60 | expect(err.uri).to.be.undefined; 61 | expect(err.status).to.equal(401); 62 | }); 63 | }); 64 | 65 | describe('constructed with a message and invalid_grant code', function() { 66 | var err = new TokenError('Invalid grant', 'invalid_grant'); 67 | 68 | it('should have default properties', function() { 69 | expect(err.message).to.equal('Invalid grant'); 70 | expect(err.code).to.equal('invalid_grant'); 71 | expect(err.uri).to.be.undefined; 72 | expect(err.status).to.equal(403); 73 | }); 74 | }); 75 | 76 | describe('constructed with a message and unauthorized_client code', function() { 77 | var err = new TokenError('Unauthorized client', 'unauthorized_client'); 78 | 79 | it('should have default properties', function() { 80 | expect(err.message).to.equal('Unauthorized client'); 81 | expect(err.code).to.equal('unauthorized_client'); 82 | expect(err.uri).to.be.undefined; 83 | expect(err.status).to.equal(403); 84 | }); 85 | }); 86 | 87 | describe('constructed with a message and unsupported_grant_type code', function() { 88 | var err = new TokenError('Unsupported grant type', 'unsupported_grant_type'); 89 | 90 | it('should have default properties', function() { 91 | expect(err.message).to.equal('Unsupported grant type'); 92 | expect(err.code).to.equal('unsupported_grant_type'); 93 | expect(err.uri).to.be.undefined; 94 | expect(err.status).to.equal(501); 95 | }); 96 | }); 97 | 98 | describe('constructed with a message and invalid_scope code', function() { 99 | var err = new TokenError('Invalid scope', 'invalid_scope'); 100 | 101 | it('should have default properties', function() { 102 | expect(err.message).to.equal('Invalid scope'); 103 | expect(err.code).to.equal('invalid_scope'); 104 | expect(err.uri).to.be.undefined; 105 | expect(err.status).to.equal(400); 106 | }); 107 | }); 108 | 109 | describe('constructed with a message, code, uri and status', function() { 110 | var err = new TokenError('Payment required', 'payment_required', 'http://www.example.com/oauth/help', 402); 111 | 112 | it('should have default properties', function() { 113 | expect(err.message).to.equal('Payment required'); 114 | expect(err.code).to.equal('payment_required'); 115 | expect(err.uri).to.equal('http://www.example.com/oauth/help'); 116 | expect(err.status).to.equal(402); 117 | }); 118 | 119 | it('should format correctly', function() { 120 | expect(err.toString()).to.equal('TokenError: Payment required'); 121 | }); 122 | }); 123 | 124 | }); 125 | -------------------------------------------------------------------------------- /test/middleware/authorizationErrorHandler.test.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai') 2 | , authorizationErrorHandler = require('../../lib/middleware/authorizationErrorHandler') 3 | , Server = require('../../lib/server'); 4 | 5 | 6 | describe('authorizationErrorHandler', function() { 7 | 8 | it('should be named token', function() { 9 | var server = new Server(); 10 | expect(authorizationErrorHandler(server).name).to.equal('authorizationErrorHandler'); 11 | }); 12 | 13 | it('should throw if constructed without a server argument', function() { 14 | expect(function() { 15 | authorizationErrorHandler(); 16 | }).to.throw(TypeError, 'oauth2orize.authorizationErrorHandler middleware requires a server argument'); 17 | }); 18 | 19 | describe('using legacy transaction store', function() { 20 | var server; 21 | 22 | before(function() { 23 | server = new Server(); 24 | 25 | server.grant('code', 'error', function(err, txn, res, next) { 26 | if (txn.req.scope != 'email') { return next(new Error('incorrect transaction argument')); } 27 | return res.redirect(txn.redirectURI + '?error_description=' + err.message); 28 | }); 29 | 30 | server.grant('fubar', 'error', function(err, txn, res, next) { 31 | return next(new Error('something else went wrong')); 32 | }); 33 | }); 34 | 35 | 36 | describe('handling an error', function() { 37 | var request, response; 38 | 39 | before(function(done) { 40 | chai.connect.use('express', authorizationErrorHandler(server)) 41 | .req(function(req) { 42 | request = req; 43 | req.query = {}; 44 | req.body = {}; 45 | req.session = {}; 46 | req.session['authorize'] = {}; 47 | req.session['authorize']['abc123'] = { protocol: 'oauth2' }; 48 | req.user = { id: 'u1234', username: 'bob' }; 49 | req.oauth2 = {}; 50 | req.oauth2.transactionID = 'abc123'; 51 | req.oauth2.client = { id: 'c5678', name: 'Example' }; 52 | req.oauth2.redirectURI = 'http://example.com/auth/callback'; 53 | req.oauth2.req = { type: 'code', scope: 'email' }; 54 | }) 55 | .end(function(res) { 56 | response = res; 57 | done(); 58 | }) 59 | .dispatch(new Error('something went wrong')); 60 | }); 61 | 62 | it('should respond', function() { 63 | expect(response.statusCode).to.equal(302); 64 | expect(response.getHeader('Location')).to.equal('http://example.com/auth/callback?error_description=something went wrong'); 65 | }); 66 | 67 | it('should remove transaction from session', function() { 68 | expect(request.session['authorize']['abc123']).to.be.undefined; 69 | }); 70 | }); 71 | 72 | describe('handling an error when transaction has not been persisted', function() { 73 | var request, response; 74 | 75 | before(function(done) { 76 | chai.connect.use('express', authorizationErrorHandler(server)) 77 | .req(function(req) { 78 | request = req; 79 | req.query = {}; 80 | req.body = {}; 81 | req.session = {}; 82 | req.session['authorize'] = {}; 83 | req.session['authorize']['abc123'] = { protocol: 'oauth2' }; 84 | req.user = { id: 'u1234', username: 'bob' }; 85 | req.oauth2 = {}; 86 | req.oauth2.client = { id: 'c5678', name: 'Example' }; 87 | req.oauth2.redirectURI = 'http://example.com/auth/callback'; 88 | req.oauth2.req = { type: 'code', scope: 'email' }; 89 | }) 90 | .end(function(res) { 91 | response = res; 92 | done(); 93 | }) 94 | .dispatch(new Error('something went wrong')); 95 | }); 96 | 97 | it('should respond', function() { 98 | expect(response.statusCode).to.equal(302); 99 | expect(response.getHeader('Location')).to.equal('http://example.com/auth/callback?error_description=something went wrong'); 100 | }); 101 | 102 | it('should not remove data from session', function() { 103 | expect(request.session['authorize']['abc123']).to.be.an('object'); 104 | }); 105 | }); 106 | 107 | describe('handling an error where res.end has already been proxied', function() { 108 | var request, response; 109 | 110 | before(function(done) { 111 | chai.connect.use('express', authorizationErrorHandler(server)) 112 | .req(function(req) { 113 | request = req; 114 | req.query = {}; 115 | req.body = {}; 116 | req.session = {}; 117 | req.session['authorize'] = {}; 118 | req.session['authorize']['abc123'] = { protocol: 'oauth2' }; 119 | req.user = { id: 'u1234', username: 'bob' }; 120 | req.oauth2 = {}; 121 | req.oauth2.transactionID = 'abc123'; 122 | req.oauth2.client = { id: 'c5678', name: 'Example' }; 123 | req.oauth2.redirectURI = 'http://example.com/auth/callback'; 124 | req.oauth2.req = { type: 'code', scope: 'email' }; 125 | req.oauth2._endProxied = true; 126 | }) 127 | .end(function(res) { 128 | response = res; 129 | done(); 130 | }) 131 | .dispatch(new Error('something went wrong')); 132 | }); 133 | 134 | it('should respond', function() { 135 | expect(response.statusCode).to.equal(302); 136 | expect(response.getHeader('Location')).to.equal('http://example.com/auth/callback?error_description=something went wrong'); 137 | }); 138 | 139 | it('should not remove transaction from session', function() { 140 | expect(request.session['authorize']['abc123']).to.be.an('object'); 141 | }); 142 | }); 143 | 144 | describe('encountering an unsupported response type while handling an error', function() { 145 | var request, response, err; 146 | 147 | before(function(done) { 148 | chai.connect.use('express', authorizationErrorHandler(server)) 149 | .req(function(req) { 150 | request = req; 151 | req.query = {}; 152 | req.body = {}; 153 | req.session = {}; 154 | req.session['authorize'] = {}; 155 | req.session['authorize']['abc123'] = { protocol: 'oauth2' }; 156 | req.user = { id: 'u1234', username: 'bob' }; 157 | req.oauth2 = {}; 158 | req.oauth2.transactionID = 'abc123'; 159 | req.oauth2.client = { id: 'c5678', name: 'Example' }; 160 | req.oauth2.redirectURI = 'http://example.com/auth/callback'; 161 | req.oauth2.req = { type: 'unsupported', scope: 'email' }; 162 | }) 163 | .next(function(e) { 164 | err = e; 165 | done(); 166 | }) 167 | .dispatch(new Error('something went wrong')); 168 | }); 169 | 170 | it('should preserve error', function() { 171 | expect(err).to.be.an.instanceOf(Error); 172 | expect(err.message).to.equal('something went wrong'); 173 | }); 174 | }); 175 | 176 | describe('encountering an error while handling an error', function() { 177 | var request, response, err; 178 | 179 | before(function(done) { 180 | chai.connect.use('express', authorizationErrorHandler(server)) 181 | .req(function(req) { 182 | request = req; 183 | req.query = {}; 184 | req.body = {}; 185 | req.session = {}; 186 | req.session['authorize'] = {}; 187 | req.session['authorize']['abc123'] = { protocol: 'oauth2' }; 188 | req.user = { id: 'u1234', username: 'bob' }; 189 | req.oauth2 = {}; 190 | req.oauth2.transactionID = 'abc123'; 191 | req.oauth2.client = { id: 'c5678', name: 'Example' }; 192 | req.oauth2.redirectURI = 'http://example.com/auth/callback'; 193 | req.oauth2.req = { type: 'fubar', scope: 'email' }; 194 | }) 195 | .next(function(e) { 196 | err = e; 197 | done(); 198 | }) 199 | .dispatch(new Error('something went wrong')); 200 | }); 201 | 202 | it('should error', function() { 203 | expect(err).to.be.an.instanceOf(Error); 204 | expect(err.message).to.equal('something else went wrong'); 205 | }); 206 | }); 207 | 208 | describe('handling a request that is not associated with a transaction', function() { 209 | var request, response, err; 210 | 211 | before(function(done) { 212 | chai.connect.use('express', authorizationErrorHandler(server)) 213 | .req(function(req) { 214 | request = req; 215 | req.query = {}; 216 | req.body = {}; 217 | req.session = {}; 218 | req.session['authorize'] = {}; 219 | req.session['authorize']['abc123'] = { protocol: 'oauth2' }; 220 | req.user = { id: 'u1234', username: 'bob' }; 221 | }) 222 | .next(function(e) { 223 | err = e; 224 | done(); 225 | }) 226 | .dispatch(new Error('something went wrong')); 227 | }); 228 | 229 | it('should preserve error', function() { 230 | expect(err).to.be.an.instanceOf(Error); 231 | expect(err.message).to.equal('something went wrong'); 232 | }); 233 | 234 | it('should not remove data from session', function() { 235 | expect(request.session['authorize']['abc123']).to.be.an('object'); 236 | }); 237 | }); 238 | }); 239 | 240 | describe('using non-legacy transaction store', function() { 241 | var server; 242 | 243 | before(function() { 244 | var MockStore = require('../mock/store'); 245 | server = new Server({ store: new MockStore() }); 246 | 247 | server.grant('code', 'error', function(err, txn, res, next) { 248 | if (txn.req.scope != 'email') { return next(new Error('incorrect transaction argument')); } 249 | return res.redirect(txn.redirectURI + '?error_description=' + err.message); 250 | }); 251 | }); 252 | 253 | describe('handling an error', function() { 254 | var request, response; 255 | 256 | before(function(done) { 257 | chai.connect.use('express', authorizationErrorHandler(server)) 258 | .req(function(req) { 259 | request = req; 260 | req.query = {}; 261 | req.body = {}; 262 | req.user = { id: 'u1234', username: 'bob' }; 263 | req.oauth2 = {}; 264 | req.oauth2.transactionID = 'abc123'; 265 | req.oauth2.client = { id: 'c5678', name: 'Example' }; 266 | req.oauth2.redirectURI = 'http://example.com/auth/callback'; 267 | req.oauth2.req = { type: 'code', scope: 'email' }; 268 | }) 269 | .end(function(res) { 270 | response = res; 271 | done(); 272 | }) 273 | .dispatch(new Error('something went wrong')); 274 | }); 275 | 276 | it('should respond', function() { 277 | expect(response.statusCode).to.equal(302); 278 | expect(response.getHeader('Location')).to.equal('http://example.com/auth/callback?error_description=something went wrong'); 279 | }); 280 | 281 | it('should remove transaction', function() { 282 | expect(request.__mock_store__.removed).to.equal('abc123'); 283 | }); 284 | }); 285 | }); 286 | 287 | }); 288 | -------------------------------------------------------------------------------- /test/middleware/token.test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it, expect, before */ 2 | /* jshint camelcase: false */ 3 | 4 | var chai = require('chai') 5 | , token = require('../../lib/middleware/token') 6 | , Server = require('../../lib/server'); 7 | 8 | 9 | describe('token', function() { 10 | 11 | it('should be named token', function() { 12 | var server = new Server(); 13 | expect(token(server).name).to.equal('token'); 14 | }); 15 | 16 | it('should throw if constructed without a server argument', function() { 17 | expect(function() { 18 | token(); 19 | }).to.throw(TypeError, 'oauth2orize.token middleware requires a server argument'); 20 | }); 21 | 22 | 23 | describe('exchanging a grant for an access token', function() { 24 | var server = new Server(); 25 | 26 | server.exchange('authorization_code', function(req, res, next) { 27 | if (req.body.code !== 'abc123') { return done(new Error('incorrect req.body argument')); } 28 | var json = JSON.stringify({ token_type: 'bearer', access_token: 'aaa-111-ccc' }); 29 | return res.end(json); 30 | }); 31 | 32 | server.exchange('fubar', function(req, res, next) { 33 | next(new Error('something went wrong')); 34 | }); 35 | 36 | 37 | describe('handling a request with supported grant', function() { 38 | var response; 39 | 40 | before(function(done) { 41 | chai.connect.use(token(server)) 42 | .req(function(req) { 43 | req.body = { grant_type: 'authorization_code', code: 'abc123' }; 44 | }) 45 | .end(function(res) { 46 | response = res; 47 | done(); 48 | }) 49 | .dispatch(); 50 | }); 51 | 52 | it('should respond', function() { 53 | expect(response.body).to.equal('{"token_type":"bearer","access_token":"aaa-111-ccc"}'); 54 | }); 55 | }); 56 | 57 | describe('handling a request with unsupported grant type', function() { 58 | var err; 59 | 60 | before(function(done) { 61 | chai.connect.use(token(server)) 62 | .req(function(req) { 63 | req.body = { grant_type: 'foo', code: 'abc123' }; 64 | }) 65 | .next(function(e) { 66 | err = e; 67 | done(); 68 | }) 69 | .dispatch(); 70 | }); 71 | 72 | it('should error', function() { 73 | expect(err).to.be.an.instanceOf(Error); 74 | expect(err.constructor.name).to.equal('TokenError'); 75 | expect(err.message).to.equal('Unsupported grant type: foo'); 76 | expect(err.code).to.equal('unsupported_grant_type'); 77 | }); 78 | }); 79 | 80 | describe('encountering an error while exchanging grant', function() { 81 | var err; 82 | 83 | before(function(done) { 84 | chai.connect.use(token(server)) 85 | .req(function(req) { 86 | req.body = { grant_type: 'fubar', code: 'abc123' }; 87 | }) 88 | .next(function(e) { 89 | err = e; 90 | done(); 91 | }) 92 | .dispatch(); 93 | }); 94 | 95 | it('should error', function() { 96 | expect(err).to.be.an.instanceOf(Error); 97 | expect(err.message).to.equal('something went wrong'); 98 | }); 99 | }); 100 | 101 | }); 102 | 103 | }); 104 | -------------------------------------------------------------------------------- /test/mock/store.js: -------------------------------------------------------------------------------- 1 | function MockStore(store) { 2 | this._store = store; 3 | } 4 | 5 | MockStore.prototype.load = function(req, cb) { 6 | process.nextTick(function() { 7 | cb(null, { transactionID: req.body.state, redirectURI: 'http://www.example.com/auth/callback' }) 8 | }); 9 | } 10 | 11 | MockStore.prototype.store = function(req, txn, cb) { 12 | req.__mock_store__ = {}; 13 | req.__mock_store__.txn = txn; 14 | process.nextTick(function() { 15 | cb(null, 'mocktxn-1') 16 | }); 17 | } 18 | 19 | MockStore.prototype.update = function(req, h, txn, cb) { 20 | req.__mock_store__ = {}; 21 | req.__mock_store__.uh = h; 22 | req.__mock_store__.utxn = txn; 23 | process.nextTick(function() { 24 | cb(null, 'mocktxn-1u') 25 | }); 26 | } 27 | 28 | MockStore.prototype.remove = function(req, h, cb) { 29 | req.__mock_store__ = {}; 30 | req.__mock_store__.removed = h; 31 | process.nextTick(function() { 32 | cb(null) 33 | }); 34 | } 35 | 36 | 37 | module.exports = MockStore; 38 | -------------------------------------------------------------------------------- /test/package.test.js: -------------------------------------------------------------------------------- 1 | var oauth2orize = require('..') 2 | , Server = require('../lib/server'); 3 | 4 | 5 | describe('oauth2orize', function() { 6 | 7 | it('should export createServer', function() { 8 | expect(oauth2orize).to.be.a('function'); 9 | expect(oauth2orize.createServer).to.be.a('function'); 10 | expect(oauth2orize).to.equal(oauth2orize.createServer); 11 | }); 12 | 13 | it('should export middleware', function() { 14 | expect(oauth2orize.errorHandler).to.be.a('function'); 15 | }); 16 | 17 | it('should export grants', function() { 18 | expect(oauth2orize.grant).to.be.an('object'); 19 | expect(oauth2orize.grant.code).to.be.a('function'); 20 | expect(oauth2orize.grant.token).to.be.a('function'); 21 | }); 22 | 23 | it('should export aliased grants', function() { 24 | expect(oauth2orize.grant.authorizationCode).to.equal(oauth2orize.grant.code); 25 | expect(oauth2orize.grant.implicit).to.equal(oauth2orize.grant.token); 26 | }); 27 | 28 | it('should export exchanges', function() { 29 | expect(oauth2orize.exchange).to.be.an('object'); 30 | expect(oauth2orize.exchange.authorizationCode).to.be.a('function'); 31 | expect(oauth2orize.exchange.clientCredentials).to.be.a('function'); 32 | expect(oauth2orize.exchange.password).to.be.a('function'); 33 | expect(oauth2orize.exchange.refreshToken).to.be.a('function'); 34 | }); 35 | 36 | it('should export aliased exchanges', function() { 37 | expect(oauth2orize.exchange.code).to.equal(oauth2orize.exchange.authorizationCode); 38 | }); 39 | 40 | it('should export Error constructors', function() { 41 | expect(oauth2orize.OAuth2Error).to.be.a('function'); 42 | expect(oauth2orize.AuthorizationError).to.be.a('function'); 43 | expect(oauth2orize.TokenError).to.be.a('function'); 44 | }); 45 | 46 | 47 | describe('.createServer', function() { 48 | it('should return a server', function() { 49 | var s = oauth2orize.createServer(); 50 | expect(s).to.be.an.instanceOf(Server); 51 | }); 52 | }); 53 | 54 | }); 55 | -------------------------------------------------------------------------------- /test/server.exchange.test.js: -------------------------------------------------------------------------------- 1 | var Server = require('../lib/server'); 2 | 3 | 4 | describe('Server', function() { 5 | 6 | describe('with no exchanges', function() { 7 | var server = new Server(); 8 | 9 | describe('handling a request', function() { 10 | var err; 11 | 12 | before(function(done) { 13 | var req = {}; 14 | var res = {}; 15 | 16 | server._exchange(undefined, req, res, function(e) { 17 | err = e; 18 | done(); 19 | }); 20 | }); 21 | 22 | it('should not error', function() { 23 | expect(err).to.be.undefined; 24 | }); 25 | }); 26 | }); 27 | 28 | describe('with one exchange registered using a named function', function() { 29 | var server = new Server(); 30 | server.exchange(code); 31 | function code(req, res, next) { 32 | if (req.code != '123') { return next(new Error('something is wrong')); } 33 | res.end('abc'); 34 | } 35 | 36 | describe('handling a request with supported type', function() { 37 | var result, err; 38 | 39 | before(function(done) { 40 | var req = { code: '123' }; 41 | var res = {}; 42 | res.end = function(data) { 43 | result = data; 44 | done(); 45 | } 46 | 47 | server._exchange('code', req, res, function(e) { 48 | done(new Error('should not be called')); 49 | }); 50 | }); 51 | 52 | it('should not error', function() { 53 | expect(err).to.be.undefined; 54 | }); 55 | 56 | it('should send response', function() { 57 | expect(result).to.equal('abc'); 58 | }); 59 | }); 60 | 61 | describe('handling a request with unsupported type', function() { 62 | var result, err; 63 | 64 | before(function(done) { 65 | var req = {}; 66 | var res = {}; 67 | res.end = function(data) { 68 | done(new Error('should not be called')); 69 | } 70 | 71 | server._exchange('unsupported', req, res, function(e) { 72 | err = e; 73 | done(); 74 | }); 75 | }); 76 | 77 | it('should not error', function() { 78 | expect(err).to.be.undefined; 79 | }); 80 | }); 81 | 82 | describe('handling a request with undefined type', function() { 83 | var result, err; 84 | 85 | before(function(done) { 86 | var req = {}; 87 | var res = {}; 88 | res.end = function(data) { 89 | done(new Error('should not be called')); 90 | } 91 | 92 | server._exchange(undefined, req, res, function(e) { 93 | err = e; 94 | done(); 95 | }); 96 | }); 97 | 98 | it('should not error', function() { 99 | expect(err).to.be.undefined; 100 | }); 101 | }); 102 | }); 103 | 104 | describe('with a wildcard exchange registered with null', function() { 105 | var server = new Server(); 106 | server.exchange(null, function(req, res, next) { 107 | if (req.code != '123') { return next(new Error('something is wrong')); } 108 | res.end('abc') 109 | }); 110 | 111 | describe('handling a request with type', function() { 112 | var result, err; 113 | 114 | before(function(done) { 115 | var req = { code: '123' }; 116 | var res = {}; 117 | res.end = function(data) { 118 | result = data; 119 | done(); 120 | } 121 | 122 | server._exchange('code', req, res, function(e) { 123 | done(new Error('should not be called')); 124 | }); 125 | }); 126 | 127 | it('should not error', function() { 128 | expect(err).to.be.undefined; 129 | }); 130 | 131 | it('should send response', function() { 132 | expect(result).to.equal('abc'); 133 | }); 134 | }); 135 | 136 | describe('handling a request without type', function() { 137 | var result, err; 138 | 139 | before(function(done) { 140 | var req = { code: '123' }; 141 | var res = {}; 142 | res.end = function(data) { 143 | result = data; 144 | done(); 145 | } 146 | 147 | server._exchange(undefined, req, res, function(e) { 148 | done(new Error('should not be called')); 149 | }); 150 | }); 151 | 152 | it('should not error', function() { 153 | expect(err).to.be.undefined; 154 | }); 155 | 156 | it('should send response', function() { 157 | expect(result).to.equal('abc'); 158 | }); 159 | }); 160 | }); 161 | 162 | describe('with a wildcard exchange registered with star', function() { 163 | var server = new Server(); 164 | server.exchange('*', function(req, res, next) { 165 | if (req.code != '123') { return next(new Error('something is wrong')); } 166 | res.end('abc') 167 | }); 168 | 169 | describe('handling a request with type', function() { 170 | var result, err; 171 | 172 | before(function(done) { 173 | var req = { code: '123' }; 174 | var res = {}; 175 | res.end = function(data) { 176 | result = data; 177 | done(); 178 | } 179 | 180 | server._exchange('code', req, res, function(e) { 181 | done(new Error('should not be called')); 182 | }); 183 | }); 184 | 185 | it('should not error', function() { 186 | expect(err).to.be.undefined; 187 | }); 188 | 189 | it('should send response', function() { 190 | expect(result).to.equal('abc'); 191 | }); 192 | }); 193 | 194 | describe('handling a request without type', function() { 195 | var result, err; 196 | 197 | before(function(done) { 198 | var req = { code: '123' }; 199 | var res = {}; 200 | res.end = function(data) { 201 | result = data; 202 | done(); 203 | } 204 | 205 | server._exchange(undefined, req, res, function(e) { 206 | done(new Error('should not be called')); 207 | }); 208 | }); 209 | 210 | it('should not error', function() { 211 | expect(err).to.be.undefined; 212 | }); 213 | 214 | it('should send response', function() { 215 | expect(result).to.equal('abc'); 216 | }); 217 | }); 218 | }); 219 | 220 | describe('with one wildcard exchange and one named exchange', function() { 221 | var server = new Server(); 222 | server.exchange('*', function(req, res, next) { 223 | if (req.code != '123') { return next(new Error('something is wrong')); } 224 | req.star = true; 225 | next(); 226 | }); 227 | server.exchange('code', function(req, res, next) { 228 | if (!req.star) { return next(new Error('something is wrong')); } 229 | res.end('abc') 230 | }); 231 | 232 | describe('handling a request with type', function() { 233 | var result, err; 234 | 235 | before(function(done) { 236 | var req = { code: '123' }; 237 | var res = {}; 238 | res.end = function(data) { 239 | result = data; 240 | done(); 241 | } 242 | 243 | server._exchange('code', req, res, function(e) { 244 | done(new Error('should not be called')); 245 | }); 246 | }); 247 | 248 | it('should not error', function() { 249 | expect(err).to.be.undefined; 250 | }); 251 | 252 | it('should send response', function() { 253 | expect(result).to.equal('abc'); 254 | }); 255 | }); 256 | }); 257 | 258 | describe('with an exchange that encounters an error', function() { 259 | var server = new Server(); 260 | server.exchange('code', function(req, res, next) { 261 | next(new Error('something went wrong')); 262 | }); 263 | 264 | describe('handling a request with type', function() { 265 | var result, err; 266 | 267 | before(function(done) { 268 | var req = { code: '123' }; 269 | var res = {}; 270 | res.end = function(data) { 271 | done(new Error('should not be called')); 272 | } 273 | 274 | server._exchange('code', req, res, function(e) { 275 | err = e; 276 | return done(); 277 | }); 278 | }); 279 | 280 | it('should error', function() { 281 | expect(err).to.be.an.instanceOf(Error); 282 | expect(err.message).to.equal('something went wrong') 283 | }); 284 | }); 285 | }); 286 | 287 | describe('with an exchange that throws an exception', function() { 288 | var server = new Server(); 289 | server.exchange('code', function(req, res, next) { 290 | throw new Error('something was thrown'); 291 | }); 292 | 293 | describe('handling a request with type', function() { 294 | var result, err; 295 | 296 | before(function(done) { 297 | var req = {}; 298 | var res = {}; 299 | res.end = function(data) { 300 | done(new Error('should not be called')); 301 | } 302 | 303 | server._exchange('code', req, res, function(e) { 304 | err = e; 305 | return done(); 306 | }); 307 | }); 308 | 309 | it('should error', function() { 310 | expect(err).to.be.an.instanceOf(Error); 311 | expect(err.message).to.equal('something was thrown') 312 | }); 313 | }); 314 | }); 315 | 316 | }); 317 | -------------------------------------------------------------------------------- /test/server.grant.test.js: -------------------------------------------------------------------------------- 1 | var Server = require('../lib/server'); 2 | 3 | 4 | describe('Server', function() { 5 | 6 | describe('registering a grant module', function() { 7 | var server = new Server(); 8 | var mod = {}; 9 | mod.name = 'foo'; 10 | mod.request = function(req) {}; 11 | mod.response = function(txn, res, next) {}; 12 | server.grant(mod); 13 | 14 | it('should have one request parser', function() { 15 | expect(server._reqParsers).to.have.length(1); 16 | var parser = server._reqParsers[0]; 17 | expect(parser.type.toString()).to.equal('foo'); 18 | expect(parser.handle).to.be.a('function'); 19 | expect(parser.handle).to.have.length(1); 20 | }); 21 | 22 | it('should have one response handler', function() { 23 | expect(server._resHandlers).to.have.length(1); 24 | var handler = server._resHandlers[0]; 25 | expect(handler.type.toString()).to.equal('foo'); 26 | expect(handler.handle).to.be.a('function'); 27 | expect(handler.handle).to.have.length(3); 28 | }); 29 | 30 | it('should not have any error handlers', function() { 31 | expect(server._errHandlers).to.have.length(0); 32 | }); 33 | }); 34 | 35 | describe('registering a grant module with error handler', function() { 36 | var server = new Server(); 37 | var mod = {}; 38 | mod.name = 'foo'; 39 | mod.request = function(req) {}; 40 | mod.response = function(txn, res, next) {}; 41 | mod.error = function(err, txn, res, next) {}; 42 | server.grant(mod); 43 | 44 | it('should have one request parser', function() { 45 | expect(server._reqParsers).to.have.length(1); 46 | var parser = server._reqParsers[0]; 47 | expect(parser.type.toString()).to.equal('foo'); 48 | expect(parser.handle).to.be.a('function'); 49 | expect(parser.handle).to.have.length(1); 50 | }); 51 | 52 | it('should have one response handler', function() { 53 | expect(server._resHandlers).to.have.length(1); 54 | var handler = server._resHandlers[0]; 55 | expect(handler.type.toString()).to.equal('foo'); 56 | expect(handler.handle).to.be.a('function'); 57 | expect(handler.handle).to.have.length(3); 58 | }); 59 | 60 | it('should have one error handler', function() { 61 | expect(server._errHandlers).to.have.length(1); 62 | var handler = server._errHandlers[0]; 63 | expect(handler.type.toString()).to.equal('foo'); 64 | expect(handler.handle).to.be.a('function'); 65 | expect(handler.handle).to.have.length(4); 66 | }); 67 | }); 68 | 69 | describe('registering a grant module by type', function() { 70 | var server = new Server(); 71 | var mod = {}; 72 | mod.name = 'foo'; 73 | mod.request = function(req) {}; 74 | mod.response = function(txn, res, next) {}; 75 | server.grant('bar', mod); 76 | 77 | it('should have one request parser', function() { 78 | expect(server._reqParsers).to.have.length(1); 79 | var parser = server._reqParsers[0]; 80 | expect(parser.type.toString()).to.equal('bar'); 81 | expect(parser.handle).to.be.a('function'); 82 | expect(parser.handle).to.have.length(1); 83 | }); 84 | 85 | it('should have one response handler', function() { 86 | expect(server._resHandlers).to.have.length(1); 87 | var handler = server._resHandlers[0]; 88 | expect(handler.type.toString()).to.equal('bar'); 89 | expect(handler.handle).to.be.a('function'); 90 | expect(handler.handle).to.have.length(3); 91 | }); 92 | 93 | it('should not have any error handlers', function() { 94 | expect(server._errHandlers).to.have.length(0); 95 | }); 96 | }); 97 | 98 | describe('registering a grant module with error handler by type', function() { 99 | var server = new Server(); 100 | var mod = {}; 101 | mod.name = 'foo'; 102 | mod.request = function(req) {}; 103 | mod.response = function(txn, res, next) {}; 104 | mod.error = function(err, txn, res, next) {}; 105 | server.grant('bar', mod); 106 | 107 | it('should have one request parser', function() { 108 | expect(server._reqParsers).to.have.length(1); 109 | var parser = server._reqParsers[0]; 110 | expect(parser.type.toString()).to.equal('bar'); 111 | expect(parser.handle).to.be.a('function'); 112 | expect(parser.handle).to.have.length(1); 113 | }); 114 | 115 | it('should have one response handler', function() { 116 | expect(server._resHandlers).to.have.length(1); 117 | var handler = server._resHandlers[0]; 118 | expect(handler.type.toString()).to.equal('bar'); 119 | expect(handler.handle).to.be.a('function'); 120 | expect(handler.handle).to.have.length(3); 121 | }); 122 | 123 | it('should have one error handler', function() { 124 | expect(server._errHandlers).to.have.length(1); 125 | var handler = server._errHandlers[0]; 126 | expect(handler.type.toString()).to.equal('bar'); 127 | expect(handler.handle).to.be.a('function'); 128 | expect(handler.handle).to.have.length(4); 129 | }); 130 | }); 131 | 132 | describe('registering a grant parsing function by type', function() { 133 | var server = new Server(); 134 | var mod = {}; 135 | server.grant('foo', function(req) {}); 136 | 137 | it('should have one request parser', function() { 138 | expect(server._reqParsers).to.have.length(1); 139 | var parser = server._reqParsers[0]; 140 | expect(parser.type.toString()).to.equal('foo'); 141 | expect(parser.handle).to.be.a('function'); 142 | expect(parser.handle).to.have.length(1); 143 | }); 144 | 145 | it('should not have any response handlers', function() { 146 | expect(server._resHandlers).to.have.length(0); 147 | }); 148 | 149 | it('should not have any error handlers', function() { 150 | expect(server._errHandlers).to.have.length(0); 151 | }); 152 | }); 153 | 154 | describe('registering a grant parsing function by type and phase', function() { 155 | var server = new Server(); 156 | var mod = {}; 157 | server.grant('foo', 'request', function(req) {}); 158 | 159 | it('should have one request parser', function() { 160 | expect(server._reqParsers).to.have.length(1); 161 | var parser = server._reqParsers[0]; 162 | expect(parser.type.toString()).to.equal('foo'); 163 | expect(parser.handle).to.be.a('function'); 164 | expect(parser.handle).to.have.length(1); 165 | }); 166 | 167 | it('should not have any response handlers', function() { 168 | expect(server._resHandlers).to.have.length(0); 169 | }); 170 | 171 | it('should not have any error handlers', function() { 172 | expect(server._errHandlers).to.have.length(0); 173 | }); 174 | }); 175 | 176 | describe('registering a wildcard grant parsing function', function() { 177 | var server = new Server(); 178 | var mod = {}; 179 | server.grant('*', function(req) {}); 180 | 181 | it('should have one request parser', function() { 182 | expect(server._reqParsers).to.have.length(1); 183 | var parser = server._reqParsers[0]; 184 | expect(parser.type).to.be.null; 185 | expect(parser.handle).to.be.a('function'); 186 | expect(parser.handle).to.have.length(1); 187 | }); 188 | 189 | it('should not have any response handlers', function() { 190 | expect(server._resHandlers).to.have.length(0); 191 | }); 192 | 193 | it('should not have any error handlers', function() { 194 | expect(server._errHandlers).to.have.length(0); 195 | }); 196 | }); 197 | 198 | describe('registering a grant responding function by type and phase', function() { 199 | var server = new Server(); 200 | var mod = {}; 201 | server.grant('foo', 'response', function(txn, res, next) {}); 202 | 203 | it('should not have any request parsers', function() { 204 | expect(server._reqParsers).to.have.length(0); 205 | }); 206 | 207 | it('should have one response handler', function() { 208 | expect(server._resHandlers).to.have.length(1); 209 | var handler = server._resHandlers[0]; 210 | expect(handler.type.toString()).to.equal('foo'); 211 | expect(handler.handle).to.be.a('function'); 212 | expect(handler.handle).to.have.length(3); 213 | }); 214 | 215 | it('should not have any error handlers', function() { 216 | expect(server._errHandlers).to.have.length(0); 217 | }); 218 | }); 219 | 220 | describe('registering a wildcard grant responding function', function() { 221 | var server = new Server(); 222 | var mod = {}; 223 | server.grant('*', 'response', function(txn, res, next) {}); 224 | 225 | it('should not have any request parsers', function() { 226 | expect(server._reqParsers).to.have.length(0); 227 | }); 228 | 229 | it('should have one response handler', function() { 230 | expect(server._resHandlers).to.have.length(1); 231 | var handler = server._resHandlers[0]; 232 | expect(handler.type).to.be.null; 233 | expect(handler.handle).to.be.a('function'); 234 | expect(handler.handle).to.have.length(3); 235 | }); 236 | 237 | it('should not have any error handlers', function() { 238 | expect(server._errHandlers).to.have.length(0); 239 | }); 240 | }); 241 | 242 | describe('registering a grant error handling function by type and phase', function() { 243 | var server = new Server(); 244 | var mod = {}; 245 | server.grant('foo', 'error', function(err, txn, res, next) {}); 246 | 247 | it('should not have any request parsers', function() { 248 | expect(server._reqParsers).to.have.length(0); 249 | }); 250 | 251 | it('should not have any response handlers', function() { 252 | expect(server._resHandlers).to.have.length(0); 253 | }); 254 | 255 | it('should have one error handler', function() { 256 | expect(server._errHandlers).to.have.length(1); 257 | var handler = server._errHandlers[0]; 258 | expect(handler.type.toString()).to.equal('foo'); 259 | expect(handler.handle).to.be.a('function'); 260 | expect(handler.handle).to.have.length(4); 261 | }); 262 | }); 263 | 264 | describe('registering a wildcard error handling function', function() { 265 | var server = new Server(); 266 | var mod = {}; 267 | server.grant('*', 'error', function(err, txn, res, next) {}); 268 | 269 | it('should not have any request parsers', function() { 270 | expect(server._reqParsers).to.have.length(0); 271 | }); 272 | 273 | it('should not have any response handlers', function() { 274 | expect(server._resHandlers).to.have.length(0); 275 | }); 276 | 277 | it('should have one error handler', function() { 278 | expect(server._errHandlers).to.have.length(1); 279 | var handler = server._errHandlers[0]; 280 | expect(handler.type).to.be.null; 281 | expect(handler.handle).to.be.a('function'); 282 | expect(handler.handle).to.have.length(4); 283 | }); 284 | }); 285 | 286 | }); 287 | -------------------------------------------------------------------------------- /test/server.request.test.js: -------------------------------------------------------------------------------- 1 | var Server = require('../lib/server'); 2 | 3 | 4 | describe('Server', function() { 5 | 6 | describe('parsing authorization requests with one supported type', function() { 7 | var server = new Server(); 8 | server.grant('foo', function(req) { 9 | return { foo: req.query.foo } 10 | }); 11 | 12 | describe('request for supported type', function() { 13 | var areq, err; 14 | 15 | before(function(done) { 16 | var req = { query: { foo: '1' } }; 17 | 18 | server._parse('foo', req, function(e, ar) { 19 | areq = ar; 20 | err = e; 21 | done(); 22 | }); 23 | }); 24 | 25 | it('should not error', function() { 26 | expect(err).to.be.null; 27 | }); 28 | 29 | it('should parse request', function() { 30 | expect(areq).to.be.an('object'); 31 | expect(Object.keys(areq)).to.have.length(2); 32 | expect(areq.type).to.equal('foo'); 33 | expect(areq.foo).to.equal('1'); 34 | }); 35 | }); 36 | 37 | describe('request for unsupported type', function() { 38 | var areq, err; 39 | 40 | before(function(done) { 41 | var req = { query: { foo: '1' } }; 42 | 43 | server._parse('bar', req, function(e, ar) { 44 | areq = ar; 45 | err = e; 46 | done(); 47 | }); 48 | }); 49 | 50 | it('should not error', function() { 51 | expect(err).to.be.null; 52 | }); 53 | 54 | it('should parse only type', function() { 55 | expect(areq).to.be.an('object'); 56 | expect(Object.keys(areq)).to.have.length(1); 57 | expect(areq.type).to.equal('bar'); 58 | }); 59 | }); 60 | 61 | describe('request for undefined type', function() { 62 | var areq, err; 63 | 64 | before(function(done) { 65 | var req = { query: { foo: '1' } }; 66 | 67 | server._parse(undefined, req, function(e, ar) { 68 | areq = ar; 69 | err = e; 70 | done(); 71 | }); 72 | }); 73 | 74 | it('should not error', function() { 75 | expect(err).to.be.null; 76 | }); 77 | 78 | it('should not parse request', function() { 79 | expect(areq).to.be.an('object'); 80 | expect(Object.keys(areq)).to.have.length(0); 81 | }); 82 | }); 83 | }); 84 | 85 | describe('parsing authorization requests with one supported type that throws an exception', function() { 86 | var server = new Server(); 87 | server.grant('foo', function(req) { 88 | throw new Error('something went horribly wrong'); 89 | }); 90 | 91 | describe('request for supported type', function() { 92 | var areq, err; 93 | 94 | before(function(done) { 95 | var req = { query: { foo: '1' } }; 96 | 97 | server._parse('foo', req, function(e, ar) { 98 | areq = ar; 99 | err = e; 100 | done(); 101 | }); 102 | }); 103 | 104 | it('should error', function() { 105 | expect(err).to.be.an.instanceOf(Error); 106 | expect(err.message).to.equal('something went horribly wrong'); 107 | }); 108 | 109 | it('should not parse object', function() { 110 | expect(areq).to.be.undefined; 111 | }); 112 | }); 113 | }); 114 | 115 | describe('parsing authorization requests with one wildcard parser', function() { 116 | var server = new Server(); 117 | server.grant('*', function(req) { 118 | return { star: req.query.star } 119 | }); 120 | 121 | describe('request for type', function() { 122 | var areq, err; 123 | 124 | before(function(done) { 125 | var req = { query: { star: 'orion' } }; 126 | 127 | server._parse('foo', req, function(e, ar) { 128 | areq = ar; 129 | err = e; 130 | done(); 131 | }); 132 | }); 133 | 134 | it('should not error', function() { 135 | expect(err).to.be.null; 136 | }); 137 | 138 | it('should parse request', function() { 139 | expect(areq).to.be.an('object'); 140 | expect(Object.keys(areq)).to.have.length(2); 141 | expect(areq.type).to.equal('foo'); 142 | expect(areq.star).to.equal('orion'); 143 | }); 144 | }); 145 | }); 146 | 147 | describe('parsing authorization requests with a wildcard parser and one supported type', function() { 148 | var server = new Server(); 149 | server.grant('*', function(req) { 150 | return { star: req.query.star } 151 | }); 152 | server.grant('bar', function(req) { 153 | return { bar: req.query.bar } 154 | }); 155 | 156 | describe('request for supported type', function() { 157 | var areq, err; 158 | 159 | before(function(done) { 160 | var req = { query: { bar: '10', star: 'orion' } }; 161 | 162 | server._parse('bar', req, function(e, ar) { 163 | areq = ar; 164 | err = e; 165 | done(); 166 | }); 167 | }); 168 | 169 | it('should not error', function() { 170 | expect(err).to.be.null; 171 | }); 172 | 173 | it('should parse request', function() { 174 | expect(areq).to.be.an('object'); 175 | expect(Object.keys(areq)).to.have.length(3); 176 | expect(areq.type).to.equal('bar'); 177 | expect(areq.star).to.equal('orion'); 178 | expect(areq.bar).to.equal('10'); 179 | }); 180 | }); 181 | }); 182 | 183 | describe('parsing authorization requests with no supported types', function() { 184 | var server = new Server(); 185 | 186 | describe('request for type', function() { 187 | var areq, err; 188 | 189 | before(function(done) { 190 | var req = {}; 191 | 192 | server._parse('foo', req, function(e, ar) { 193 | areq = ar; 194 | err = e; 195 | done(); 196 | }); 197 | }); 198 | 199 | it('should not error', function() { 200 | expect(err).to.be.null; 201 | }); 202 | 203 | it('should parse only type', function() { 204 | expect(areq).to.be.an('object'); 205 | expect(Object.keys(areq)).to.have.length(1); 206 | expect(areq.type).to.equal('foo'); 207 | }); 208 | }); 209 | 210 | describe('request for undefined type', function() { 211 | var areq, err; 212 | 213 | before(function(done) { 214 | var req = {}; 215 | 216 | server._parse(undefined, req, function(e, ar) { 217 | areq = ar; 218 | err = e; 219 | done(); 220 | }); 221 | }); 222 | 223 | it('should not error', function() { 224 | expect(err).to.be.null; 225 | }); 226 | 227 | it('should not parse request', function() { 228 | expect(areq).to.be.an('object'); 229 | expect(Object.keys(areq)).to.have.length(0); 230 | }); 231 | }); 232 | }); 233 | 234 | describe('parsing requests with a sync wildcard parser that throws an exception preceeding one supported type', function() { 235 | var server = new Server(); 236 | server.grant('*', function(req) { 237 | throw new Error('something went horribly wrong'); 238 | }); 239 | server.grant('bar', function(req) { 240 | return { bar: req.query.bar } 241 | }) 242 | 243 | describe('request for supported type', function() { 244 | var areq, err; 245 | 246 | before(function(done) { 247 | var req = { query: { bar: '10', star: 'orion' } }; 248 | 249 | server._parse('bar', req, function(e, ar) { 250 | areq = ar; 251 | err = e; 252 | done(); 253 | }); 254 | }); 255 | 256 | it('should error', function() { 257 | expect(err).to.be.an.instanceOf(Error); 258 | expect(err.message).to.equal('something went horribly wrong'); 259 | }); 260 | 261 | it('should not parse object', function() { 262 | expect(areq).to.be.undefined; 263 | }); 264 | }); 265 | }); 266 | 267 | describe('parsing authorization requests with an async wildcard parser preceeding one supported type', function() { 268 | var server = new Server(); 269 | server.grant('*', function(req, done) { 270 | return done(null, { star: req.query.star }); 271 | }); 272 | server.grant('bar', function(req) { 273 | return { bar: req.query.bar } 274 | }) 275 | 276 | describe('request for supported type', function() { 277 | var areq, err; 278 | 279 | before(function(done) { 280 | var req = { query: { bar: '10', star: 'orion' } }; 281 | 282 | server._parse('bar', req, function(e, ar) { 283 | areq = ar; 284 | err = e; 285 | done(); 286 | }); 287 | }); 288 | 289 | it('should not error', function() { 290 | expect(err).to.be.null; 291 | }); 292 | 293 | it('should parse request', function() { 294 | expect(areq).to.be.an('object'); 295 | expect(Object.keys(areq)).to.have.length(3); 296 | expect(areq.type).to.equal('bar'); 297 | expect(areq.star).to.equal('orion'); 298 | expect(areq.bar).to.equal('10'); 299 | }); 300 | }); 301 | }); 302 | 303 | describe('parsing requests with an async wildcard parser that encounters an error preceeding one supported type', function() { 304 | var server = new Server(); 305 | server.grant('*', function(req, done) { 306 | return done(new Error('something went wrong')); 307 | }); 308 | server.grant('bar', function(req) { 309 | return { bar: req.query.bar } 310 | }); 311 | 312 | describe('request for supported type', function() { 313 | var areq, err; 314 | 315 | before(function(done) { 316 | var req = { query: { bar: '10', star: 'orion' } }; 317 | 318 | server._parse('bar', req, function(e, ar) { 319 | areq = ar; 320 | err = e; 321 | done(); 322 | }); 323 | }); 324 | 325 | it('should error', function() { 326 | expect(err).to.be.an.instanceOf(Error); 327 | expect(err.message).to.equal('something went wrong'); 328 | }); 329 | 330 | it('should not parse object', function() { 331 | expect(areq).to.be.undefined; 332 | }); 333 | }); 334 | }); 335 | 336 | }); 337 | -------------------------------------------------------------------------------- /test/server.response.error.test.js: -------------------------------------------------------------------------------- 1 | var Server = require('../lib/server'); 2 | 3 | 4 | describe('Server', function() { 5 | 6 | describe('handling authorization error with one supported type', function() { 7 | var server = new Server(); 8 | server.grant('foo', 'error', function(err, txn, res, next) { 9 | if (txn.req.scope != 'read') { return next(new Error('something is wrong')); } 10 | res.end('error: ' + err.message); 11 | }); 12 | 13 | describe('response to supported type', function() { 14 | var result, err; 15 | 16 | before(function(done) { 17 | var txn = { req: { type: 'foo', scope: 'read' } }; 18 | var res = {}; 19 | res.end = function(data) { 20 | result = data; 21 | done(); 22 | } 23 | 24 | server._respondError(new Error('something went wrong'), txn, res, function(e) { 25 | done(new Error('should not be called')); 26 | }); 27 | }); 28 | 29 | it('should not error', function() { 30 | expect(err).to.be.undefined; 31 | }); 32 | 33 | it('should send response', function() { 34 | expect(result).to.equal('error: something went wrong'); 35 | }); 36 | }); 37 | 38 | describe('response to unsupported type', function() { 39 | var result, err; 40 | 41 | before(function(done) { 42 | var txn = { req: { type: 'unsupported' } }; 43 | var res = {}; 44 | res.end = function(data) { 45 | done(new Error('should not be called')); 46 | } 47 | 48 | server._respondError(new Error('something went wrong'), txn, res, function(e) { 49 | err = e; 50 | done(); 51 | }); 52 | }); 53 | 54 | it('should preserve error', function() { 55 | expect(err).to.be.an.instanceOf(Error); 56 | expect(err.message).to.equal('something went wrong'); 57 | }); 58 | }); 59 | }); 60 | 61 | describe('handling authorization error with responder that throws an exception', function() { 62 | var server = new Server(); 63 | server.grant('foo', 'error', function(err, txn, res, next) { 64 | throw new Error('something else went horribly wrong'); 65 | }); 66 | 67 | 68 | describe('response to supported type', function() { 69 | var result, err; 70 | 71 | before(function(done) { 72 | var txn = { req: { type: 'foo' } }; 73 | var res = {}; 74 | res.end = function(data) { 75 | done(new Error('should not be called')); 76 | } 77 | 78 | server._respondError(new Error('something went wrong'), txn, res, function(e) { 79 | err = e; 80 | done(); 81 | }); 82 | }); 83 | 84 | it('should error', function() { 85 | expect(err).to.be.an.instanceOf(Error); 86 | expect(err.message).to.equal('something else went horribly wrong'); 87 | }); 88 | }); 89 | }); 90 | 91 | describe('handling authorization error with no supported types', function() { 92 | var server = new Server(); 93 | 94 | 95 | describe('response to unsupported type', function() { 96 | var result, err; 97 | 98 | before(function(done) { 99 | var txn = { req: { type: 'foo' } }; 100 | var res = {}; 101 | res.end = function(data) { 102 | done(new Error('should not be called')); 103 | } 104 | 105 | server._respondError(new Error('something went wrong'), txn, res, function(e) { 106 | err = e; 107 | done(); 108 | }); 109 | }); 110 | 111 | it('should preserve error', function() { 112 | expect(err).to.be.an.instanceOf(Error); 113 | expect(err.message).to.equal('something went wrong'); 114 | }); 115 | }); 116 | }); 117 | 118 | }); 119 | -------------------------------------------------------------------------------- /test/server.response.test.js: -------------------------------------------------------------------------------- 1 | var Server = require('../lib/server'); 2 | 3 | 4 | describe('Server', function() { 5 | 6 | describe('handling authorization response with one supported type', function() { 7 | var server = new Server(); 8 | server.grant('foo', 'response', function(txn, res, next) { 9 | if (txn.req.scope != 'read') { return next(new Error('something is wrong')); } 10 | res.end('abc'); 11 | }); 12 | 13 | describe('response to supported type', function() { 14 | var result, err; 15 | 16 | before(function(done) { 17 | var txn = { req: { type: 'foo', scope: 'read' } }; 18 | var res = {}; 19 | res.end = function(data) { 20 | result = data; 21 | done(); 22 | } 23 | 24 | server._respond(txn, res, function(e) { 25 | done(new Error('should not be called')); 26 | }); 27 | }); 28 | 29 | it('should not error', function() { 30 | expect(err).to.be.undefined; 31 | }); 32 | 33 | it('should send response', function() { 34 | expect(result).to.equal('abc'); 35 | }); 36 | }); 37 | 38 | describe('response to unsupported type', function() { 39 | var result, err; 40 | 41 | before(function(done) { 42 | var txn = { req: { type: 'unsupported' } }; 43 | var res = {}; 44 | res.end = function(data) { 45 | done(new Error('should not be called')); 46 | } 47 | 48 | server._respond(txn, res, function(e) { 49 | err = e; 50 | done(); 51 | }); 52 | }); 53 | 54 | it('should not error', function() { 55 | expect(err).to.be.undefined; 56 | }); 57 | }); 58 | }); 59 | 60 | describe('handling authorization response with one wildcard responder', function() { 61 | var server = new Server(); 62 | server.grant('*', 'response', function(txn, res, next) { 63 | res.end('abc'); 64 | }); 65 | 66 | describe('response to any type', function() { 67 | var result, err; 68 | 69 | before(function(done) { 70 | var txn = { req: { type: 'foo', scope: 'read' } }; 71 | var res = {}; 72 | res.end = function(data) { 73 | result = data; 74 | done(); 75 | } 76 | 77 | server._respond(txn, res, function(e) { 78 | done(new Error('should not be called')); 79 | }); 80 | }); 81 | 82 | it('should not error', function() { 83 | expect(err).to.be.undefined; 84 | }); 85 | 86 | it('should send response', function() { 87 | expect(result).to.equal('abc'); 88 | }); 89 | }); 90 | }); 91 | 92 | describe('handling authorization response with one wildcard responder and one supported type', function() { 93 | var server = new Server(); 94 | server.grant('*', 'response', function(txn, res, next) { 95 | res.star = true; 96 | next(); 97 | }); 98 | server.grant('foo', 'response', function(txn, res, next) { 99 | if (!res.star) { return next(new Error('something is wrong')); } 100 | res.end('abc'); 101 | }); 102 | 103 | describe('response to supported type', function() { 104 | var response, result, err; 105 | 106 | before(function(done) { 107 | var txn = { req: { type: 'foo', scope: 'read' } }; 108 | var res = {}; 109 | res.end = function(data) { 110 | result = data; 111 | done(); 112 | } 113 | 114 | response = res; 115 | server._respond(txn, res, function(e) { 116 | done(new Error('should not be called')); 117 | }); 118 | }); 119 | 120 | it('should not error', function() { 121 | expect(err).to.be.undefined; 122 | }); 123 | 124 | it('should send response', function() { 125 | expect(result).to.equal('abc'); 126 | }); 127 | }); 128 | }); 129 | 130 | describe('handling authorization response with responder that encounters an error', function() { 131 | var server = new Server(); 132 | server.grant('foo', 'response', function(txn, res, next) { 133 | next(new Error('something went wrong')) 134 | }); 135 | 136 | describe('response to supported type', function() { 137 | var result, err; 138 | 139 | before(function(done) { 140 | var txn = { req: { type: 'foo', scope: 'read' } }; 141 | var res = {}; 142 | res.end = function(data) { 143 | done(new Error('should not be called')); 144 | } 145 | 146 | server._respond(txn, res, function(e) { 147 | err = e; 148 | done(); 149 | }); 150 | }); 151 | 152 | it('should error', function() { 153 | expect(err).to.be.an.instanceOf(Error); 154 | expect(err.message).to.equal('something went wrong'); 155 | }); 156 | }); 157 | }); 158 | 159 | describe('handling authorization response with responder that throws an exception', function() { 160 | var server = new Server(); 161 | server.grant('foo', 'response', function(txn, res, next) { 162 | throw new Error('something was thrown'); 163 | }); 164 | 165 | describe('response to supported type', function() { 166 | var result, err; 167 | 168 | before(function(done) { 169 | var txn = { req: { type: 'foo', scope: 'read' } }; 170 | var res = {}; 171 | res.end = function(data) { 172 | done(new Error('should not be called')); 173 | } 174 | 175 | server._respond(txn, res, function(e) { 176 | err = e; 177 | done(); 178 | }); 179 | }); 180 | 181 | it('should error', function() { 182 | expect(err).to.be.an.instanceOf(Error); 183 | expect(err.message).to.equal('something was thrown'); 184 | }); 185 | }); 186 | }); 187 | 188 | describe('handling authorization response with no supported types', function() { 189 | var server = new Server(); 190 | 191 | describe('response to unsupported type', function() { 192 | var err; 193 | 194 | before(function(done) { 195 | var txn = { req: { type: 'foo', scope: 'read' } }; 196 | var res = {}; 197 | 198 | server._respond(txn, res, function(e) { 199 | err = e; 200 | done(); 201 | }); 202 | }); 203 | 204 | it('should not error', function() { 205 | expect(err).to.be.undefined; 206 | }); 207 | }); 208 | }); 209 | 210 | }); 211 | -------------------------------------------------------------------------------- /test/server.serialization.test.js: -------------------------------------------------------------------------------- 1 | var Server = require('../lib/server'); 2 | 3 | 4 | describe('Server', function() { 5 | 6 | describe('#serializeClient', function() { 7 | 8 | describe('no serializers', function() { 9 | var server = new Server(); 10 | 11 | describe('serializing', function() { 12 | var obj, err; 13 | 14 | before(function(done) { 15 | server.serializeClient({ id: '1', name: 'Foo' }, function(e, o) { 16 | err = e; 17 | obj = o; 18 | return done(); 19 | }); 20 | }); 21 | 22 | it('should error', function() { 23 | expect(err).to.be.an.instanceOf(Error); 24 | expect(err.message).to.equal('Failed to serialize client. Register serialization function using serializeClient().') 25 | }); 26 | }); 27 | }); 28 | 29 | describe('one serializer', function() { 30 | var server = new Server(); 31 | server.serializeClient(function(client, done) { 32 | done(null, client.id); 33 | }); 34 | 35 | describe('serializing', function() { 36 | var obj, err; 37 | 38 | before(function(done) { 39 | server.serializeClient({ id: '1', name: 'Foo' }, function(e, o) { 40 | err = e; 41 | obj = o; 42 | return done(); 43 | }); 44 | }); 45 | 46 | it('should not error', function() { 47 | expect(err).to.be.null; 48 | }); 49 | 50 | it('should serialize', function() { 51 | expect(obj).to.equal('1'); 52 | }); 53 | }); 54 | }); 55 | 56 | describe('multiple serializers', function() { 57 | var server = new Server(); 58 | server.serializeClient(function(client, done) { 59 | done('pass'); 60 | }); 61 | server.serializeClient(function(client, done) { 62 | done(null, '#2'); 63 | }); 64 | server.serializeClient(function(client, done) { 65 | done(null, '#3'); 66 | }); 67 | 68 | describe('serializing', function() { 69 | var obj, err; 70 | 71 | before(function(done) { 72 | server.serializeClient({ id: '1', name: 'Foo' }, function(e, o) { 73 | err = e; 74 | obj = o; 75 | return done(); 76 | }); 77 | }); 78 | 79 | it('should not error', function() { 80 | expect(err).to.be.null; 81 | }); 82 | 83 | it('should serialize', function() { 84 | expect(obj).to.equal('#2'); 85 | }); 86 | }); 87 | }); 88 | 89 | describe('serializer that encounters an error', function() { 90 | var server = new Server(); 91 | server.serializeClient(function(client, done) { 92 | return done(new Error('something went wrong')); 93 | }); 94 | 95 | describe('serializing', function() { 96 | var obj, err; 97 | 98 | before(function(done) { 99 | server.serializeClient({ id: '1', name: 'Foo' }, function(e, o) { 100 | err = e; 101 | obj = o; 102 | return done(); 103 | }); 104 | }); 105 | 106 | it('should error', function() { 107 | expect(err).to.be.an.instanceOf(Error); 108 | expect(err.message).to.equal('something went wrong'); 109 | }); 110 | }); 111 | }); 112 | 113 | describe('serializer that throws an exception', function() { 114 | var server = new Server(); 115 | server.serializeClient(function(client, done) { 116 | throw new Error('something was thrown') 117 | }); 118 | 119 | describe('serializing', function() { 120 | var obj, err; 121 | 122 | before(function(done) { 123 | server.serializeClient({ id: '1', name: 'Foo' }, function(e, o) { 124 | err = e; 125 | obj = o; 126 | return done(); 127 | }); 128 | }); 129 | 130 | it('should error', function() { 131 | expect(err).to.be.an.instanceOf(Error); 132 | expect(err.message).to.equal('something was thrown'); 133 | }); 134 | }); 135 | }); 136 | 137 | }); // #serializeClient 138 | 139 | describe('#deserializeClient', function() { 140 | 141 | describe('no deserializers', function() { 142 | var server = new Server(); 143 | 144 | describe('deserializing', function() { 145 | var obj, err; 146 | 147 | before(function(done) { 148 | server.deserializeClient('1', function(e, o) { 149 | err = e; 150 | obj = o; 151 | return done(); 152 | }); 153 | }); 154 | 155 | it('should error', function() { 156 | expect(err).to.be.an.instanceOf(Error); 157 | expect(err.message).to.equal('Failed to deserialize client. Register deserialization function using deserializeClient().') 158 | }); 159 | }); 160 | }); 161 | 162 | describe('one deserializer', function() { 163 | var server = new Server(); 164 | server.deserializeClient(function(id, done) { 165 | done(null, { id: id }); 166 | }); 167 | 168 | describe('deserializing', function() { 169 | var obj, err; 170 | 171 | before(function(done) { 172 | server.deserializeClient('1', function(e, o) { 173 | err = e; 174 | obj = o; 175 | return done(); 176 | }); 177 | }); 178 | 179 | it('should not error', function() { 180 | expect(err).to.be.null; 181 | }); 182 | 183 | it('should deserialize', function() { 184 | expect(obj.id).to.equal('1'); 185 | }); 186 | }); 187 | }); 188 | 189 | describe('multiple deserializers', function() { 190 | var server = new Server(); 191 | server.deserializeClient(function(id, done) { 192 | done('pass'); 193 | }); 194 | server.deserializeClient(function(id, done) { 195 | done(null, { id: '#2' }); 196 | }); 197 | server.deserializeClient(function(id, done) { 198 | done(null, { id: '#3' }); 199 | }); 200 | 201 | describe('deserializing', function() { 202 | var obj, err; 203 | 204 | before(function(done) { 205 | server.deserializeClient('1', function(e, o) { 206 | err = e; 207 | obj = o; 208 | return done(); 209 | }); 210 | }); 211 | 212 | it('should not error', function() { 213 | expect(err).to.be.null; 214 | }); 215 | 216 | it('should deserialize', function() { 217 | expect(obj.id).to.equal('#2'); 218 | }); 219 | }); 220 | }); 221 | 222 | describe('one deserializer to null', function() { 223 | var server = new Server(); 224 | server.deserializeClient(function(id, done) { 225 | done(null, null); 226 | }); 227 | 228 | describe('deserializing', function() { 229 | var obj, err; 230 | 231 | before(function(done) { 232 | server.deserializeClient('1', function(e, o) { 233 | err = e; 234 | obj = o; 235 | return done(); 236 | }); 237 | }); 238 | 239 | it('should not error', function() { 240 | expect(err).to.be.null; 241 | }); 242 | 243 | it('should invalidate client', function() { 244 | expect(obj).to.be.false; 245 | }); 246 | }); 247 | }); 248 | 249 | describe('one deserializer to false', function() { 250 | var server = new Server(); 251 | server.deserializeClient(function(id, done) { 252 | done(null, false); 253 | }); 254 | 255 | describe('deserializing', function() { 256 | var obj, err; 257 | 258 | before(function(done) { 259 | server.deserializeClient('1', function(e, o) { 260 | err = e; 261 | obj = o; 262 | return done(); 263 | }); 264 | }); 265 | 266 | it('should not error', function() { 267 | expect(err).to.be.null; 268 | }); 269 | 270 | it('should invalidate client', function() { 271 | expect(obj).to.be.false; 272 | }); 273 | }); 274 | }); 275 | 276 | describe('multiple deserializers to null', function() { 277 | var server = new Server(); 278 | server.deserializeClient(function(obj, done) { 279 | done('pass'); 280 | }); 281 | server.deserializeClient(function(id, done) { 282 | done(null, null); 283 | }); 284 | server.deserializeClient(function(obj, done) { 285 | done(null, { id: '#3' }); 286 | }); 287 | 288 | describe('deserializing', function() { 289 | var obj, err; 290 | 291 | before(function(done) { 292 | server.deserializeClient('1', function(e, o) { 293 | err = e; 294 | obj = o; 295 | return done(); 296 | }); 297 | }); 298 | 299 | it('should not error', function() { 300 | expect(err).to.be.null; 301 | }); 302 | 303 | it('should invalidate client', function() { 304 | expect(obj).to.be.false; 305 | }); 306 | }); 307 | }); 308 | 309 | describe('multiple deserializers to false', function() { 310 | var server = new Server(); 311 | server.deserializeClient(function(obj, done) { 312 | done('pass'); 313 | }); 314 | server.deserializeClient(function(id, done) { 315 | done(null, false); 316 | }); 317 | server.deserializeClient(function(obj, done) { 318 | done(null, { id: '#3' }); 319 | }); 320 | 321 | describe('deserializing', function() { 322 | var obj, err; 323 | 324 | before(function(done) { 325 | server.deserializeClient('1', function(e, o) { 326 | err = e; 327 | obj = o; 328 | return done(); 329 | }); 330 | }); 331 | 332 | it('should not error', function() { 333 | expect(err).to.be.null; 334 | }); 335 | 336 | it('should invalidate client', function() { 337 | expect(obj).to.be.false; 338 | }); 339 | }); 340 | }); 341 | 342 | describe('deserializer that encounters an error', function() { 343 | var server = new Server(); 344 | server.deserializeClient(function(obj, done) { 345 | return done(new Error('something went wrong')); 346 | }); 347 | 348 | describe('deserializing', function() { 349 | var obj, err; 350 | 351 | before(function(done) { 352 | server.deserializeClient('1', function(e, o) { 353 | err = e; 354 | obj = o; 355 | return done(); 356 | }); 357 | }); 358 | 359 | it('should error', function() { 360 | expect(err).to.be.an.instanceOf(Error); 361 | expect(err.message).to.equal('something went wrong') 362 | }); 363 | }); 364 | }); 365 | 366 | describe('deserializer that throws an exception', function() { 367 | var server = new Server(); 368 | server.deserializeClient(function(obj, done) { 369 | throw new Error('something was thrown'); 370 | }); 371 | 372 | describe('deserializing', function() { 373 | var obj, err; 374 | 375 | before(function(done) { 376 | server.deserializeClient('1', function(e, o) { 377 | err = e; 378 | obj = o; 379 | return done(); 380 | }); 381 | }); 382 | 383 | it('should error', function() { 384 | expect(err).to.be.an.instanceOf(Error); 385 | expect(err.message).to.equal('something was thrown') 386 | }); 387 | }); 388 | }); 389 | 390 | }); // #deserializeClient 391 | 392 | }); 393 | -------------------------------------------------------------------------------- /test/server.test.js: -------------------------------------------------------------------------------- 1 | var Server = require('../lib/server'); 2 | 3 | 4 | describe('Server', function() { 5 | 6 | describe('newly initialized instance', function() { 7 | var server = new Server(); 8 | 9 | it('should wrap authorization middleware', function() { 10 | expect(server.authorization).to.be.a('function'); 11 | expect(server.authorization).to.have.length(4); 12 | expect(server.authorize).to.equal(server.authorization); 13 | }); 14 | 15 | it('should wrap resume middleware', function() { 16 | expect(server.resume).to.be.a('function'); 17 | expect(server.resume).to.have.length(3); 18 | }); 19 | 20 | it('should wrap decision middleware', function() { 21 | expect(server.decision).to.be.a('function'); 22 | expect(server.decision).to.have.length(3); 23 | }); 24 | 25 | it('should wrap authorizationErrorHandler middleware', function() { 26 | expect(server.authorizationErrorHandler).to.be.a('function'); 27 | expect(server.authorizationErrorHandler).to.have.length(1); 28 | expect(server.authorizeError).to.equal(server.authorizationErrorHandler); 29 | expect(server.authorizationError).to.equal(server.authorizationErrorHandler); 30 | }); 31 | 32 | it('should wrap token middleware', function() { 33 | expect(server.token).to.be.a('function'); 34 | expect(server.token).to.have.length(1); 35 | }); 36 | 37 | it('should wrap errorHandler middleware', function() { 38 | expect(server.errorHandler).to.be.a('function'); 39 | expect(server.errorHandler).to.have.length(1); 40 | }); 41 | 42 | it('should have no request parsers', function() { 43 | expect(server._reqParsers).to.have.length(0); 44 | }); 45 | 46 | it('should have no response handlers', function() { 47 | expect(server._resHandlers).to.have.length(0); 48 | }); 49 | 50 | it('should have no error handlers', function() { 51 | expect(server._errHandlers).to.have.length(0); 52 | }); 53 | 54 | it('should have no exchanges', function() { 55 | expect(server._exchanges).to.have.length(0); 56 | }); 57 | 58 | it('should have no serializers or deserializers', function() { 59 | expect(server._serializers).to.have.length(0); 60 | expect(server._deserializers).to.have.length(0); 61 | }); 62 | }); 63 | 64 | describe('#authorization', function() { 65 | var server = new Server(); 66 | 67 | it('should create function handler', function() { 68 | var handler = server.authorization(function(){}); 69 | expect(handler).to.be.an('function'); 70 | expect(handler).to.have.length(3); 71 | }); 72 | }); 73 | 74 | describe('#resume', function() { 75 | var server = new Server(); 76 | 77 | it('should create handler stack with two functions', function() { 78 | var handler = server.resume(function(){}); 79 | expect(handler).to.be.an('array'); 80 | expect(handler).to.have.length(2); 81 | expect(handler[0]).to.be.a('function'); 82 | expect(handler[0]).to.have.length(3); 83 | expect(handler[1]).to.be.a('function'); 84 | expect(handler[1]).to.have.length(3); 85 | }); 86 | 87 | it('should create function handler when transaction loader is disabled', function() { 88 | var handler = server.resume({ loadTransaction: false }, function(){}); 89 | expect(handler).to.be.an('function'); 90 | expect(handler).to.have.length(3); 91 | }); 92 | 93 | it('should create handler stack with custom transaction loader', function() { 94 | function loadTransaction(req, res, next) {}; 95 | var handler = server.resume({ loadTransaction: loadTransaction }, function(){}); 96 | expect(handler).to.be.an('array'); 97 | expect(handler).to.have.length(2); 98 | expect(handler[0]).to.be.a('function'); 99 | expect(handler[0].name).to.equal('loadTransaction'); 100 | expect(handler[0]).to.have.length(3); 101 | expect(handler[1]).to.be.a('function'); 102 | expect(handler[1]).to.have.length(3); 103 | }); 104 | 105 | it('should create handler stack with custom transaction loader using non-object signature', function() { 106 | function loadTransaction(req, res, next) {}; 107 | var handler = server.resume(loadTransaction, function(){}, function(){}); 108 | expect(handler).to.be.an('array'); 109 | expect(handler).to.have.length(2); 110 | expect(handler[0]).to.be.a('function'); 111 | expect(handler[0].name).to.equal('loadTransaction'); 112 | expect(handler[0]).to.have.length(3); 113 | expect(handler[1]).to.be.a('function'); 114 | expect(handler[1]).to.have.length(3); 115 | }); 116 | }); 117 | 118 | describe('#decision', function() { 119 | var server = new Server(); 120 | 121 | it('should create handler stack with two functions', function() { 122 | var handler = server.decision(); 123 | expect(handler).to.be.an('array'); 124 | expect(handler).to.have.length(2); 125 | expect(handler[0]).to.be.a('function'); 126 | expect(handler[0]).to.have.length(3); 127 | expect(handler[1]).to.be.a('function'); 128 | expect(handler[1]).to.have.length(3); 129 | }); 130 | 131 | it('should create function handler when transaction loader is disabled', function() { 132 | var handler = server.decision({ loadTransaction: false }); 133 | expect(handler).to.be.an('function'); 134 | expect(handler).to.have.length(3); 135 | }); 136 | }); 137 | 138 | describe('#authorizationErrorHandler', function() { 139 | var server = new Server(); 140 | 141 | it('should create function error handler', function() { 142 | var handler = server.authorizationErrorHandler(); 143 | expect(handler).to.be.an('array'); 144 | expect(handler).to.have.length(2); 145 | expect(handler[0]).to.be.a('function'); 146 | expect(handler[0]).to.have.length(4); 147 | expect(handler[1]).to.be.a('function'); 148 | expect(handler[1]).to.have.length(4); 149 | }); 150 | }); 151 | 152 | describe('#token', function() { 153 | var server = new Server(); 154 | 155 | it('should create function handler', function() { 156 | var handler = server.token(); 157 | expect(handler).to.be.an('function'); 158 | expect(handler).to.have.length(3); 159 | }); 160 | }); 161 | 162 | describe('#errorHandler', function() { 163 | var server = new Server(); 164 | 165 | it('should create function error handler', function() { 166 | var handler = server.errorHandler(); 167 | expect(handler).to.be.an('function'); 168 | expect(handler).to.have.length(4); 169 | }); 170 | }); 171 | 172 | }); 173 | -------------------------------------------------------------------------------- /test/unorderedlist.test.js: -------------------------------------------------------------------------------- 1 | var UnorderedList = require('../lib/unorderedlist'); 2 | 3 | 4 | describe('UnorderedList', function() { 5 | 6 | describe('constructed with a single element array', function() { 7 | var ul = new UnorderedList(['a']); 8 | 9 | it('should have correct length', function() { 10 | expect(ul).to.have.length(1); 11 | expect(ul._items[0]).to.equal('a'); 12 | }); 13 | 14 | it('should convert to string', function() { 15 | expect(ul.toString()).to.equal('a'); 16 | }); 17 | 18 | it('should be equal to list with same item', function() { 19 | var other = new UnorderedList(['a']); 20 | expect(ul.equalTo(other)).to.be.true; 21 | }); 22 | 23 | it('should be equal to array with same item', function() { 24 | expect(ul.equalTo(['a'])).to.be.true; 25 | }); 26 | 27 | it('should not be equal to list with superset of item', function() { 28 | var other = new UnorderedList(['a', 'b']); 29 | expect(ul.equalTo(other)).to.be.false; 30 | }); 31 | 32 | it('should check if it contains element', function() { 33 | expect(ul.contains('a')).to.be.true; 34 | expect(ul.contains('b')).to.be.false; 35 | }); 36 | 37 | it('should check if it contains any element', function() { 38 | expect(ul.containsAny(['a'])).to.be.true; 39 | expect(ul.containsAny(['b'])).to.be.false; 40 | expect(ul.containsAny(['1', 'a'])).to.be.true; 41 | expect(ul.containsAny(['2', 'b'])).to.be.false; 42 | }); 43 | }); 44 | 45 | describe('constructed with a multiple element array', function() { 46 | var ul = new UnorderedList(['a', 'b']); 47 | 48 | it('should have correct length', function() { 49 | expect(ul).to.have.length(2); 50 | expect(ul._items[0]).to.equal('a'); 51 | expect(ul._items[1]).to.equal('b'); 52 | }); 53 | 54 | it('should convert to string', function() { 55 | expect(ul.toString()).to.equal('a b'); 56 | }); 57 | 58 | it('should be equal to list with same set of items', function() { 59 | var other = new UnorderedList(['a', 'b']); 60 | expect(ul.equalTo(other)).to.be.true; 61 | }); 62 | 63 | it('should be equal to list with same set of items in different order', function() { 64 | var other = new UnorderedList(['b', 'a']); 65 | expect(ul.equalTo(other)).to.be.true; 66 | }); 67 | 68 | it('should not be equal to list with subset of items', function() { 69 | var other = new UnorderedList(['a']); 70 | expect(ul.equalTo(other)).to.be.false; 71 | }); 72 | 73 | it('should not be equal to list with superset of items', function() { 74 | var other = new UnorderedList(['a', 'b', 'c']); 75 | expect(ul.equalTo(other)).to.be.false; 76 | }); 77 | 78 | it('should check if it contains element', function() { 79 | expect(ul.contains('a')).to.be.true; 80 | expect(ul.contains('b')).to.be.true; 81 | expect(ul.contains('c')).to.be.false; 82 | }); 83 | 84 | it('should check if it contains any element', function() { 85 | expect(ul.containsAny(['a'])).to.be.true; 86 | expect(ul.containsAny(['b'])).to.be.true; 87 | expect(ul.containsAny(['c'])).to.be.false; 88 | expect(ul.containsAny(['1', 'a'])).to.be.true; 89 | expect(ul.containsAny(['2', 'b'])).to.be.true; 90 | expect(ul.containsAny(['3', 'c'])).to.be.false; 91 | }); 92 | }); 93 | 94 | describe('constructed with string', function() { 95 | var ul = new UnorderedList('foobar'); 96 | 97 | it('should have correct length', function() { 98 | expect(ul).to.have.length(1); 99 | expect(ul._items[0]).to.equal('foobar'); 100 | }); 101 | 102 | it('should convert to string', function() { 103 | expect(ul.toString()).to.equal('foobar'); 104 | }); 105 | }); 106 | 107 | describe('constructed with space separated string', function() { 108 | var ul = new UnorderedList('foo bar'); 109 | 110 | it('should have correct length', function() { 111 | expect(ul).to.have.length(2); 112 | expect(ul._items[0]).to.equal('foo'); 113 | expect(ul._items[1]).to.equal('bar'); 114 | }); 115 | 116 | it('should convert to string', function() { 117 | expect(ul.toString()).to.equal('foo bar'); 118 | }); 119 | }); 120 | 121 | }); 122 | --------------------------------------------------------------------------------