├── .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 | [](https://travis-ci.org/jaredhanson/oauth2orize)
21 | [](https://coveralls.io/r/jaredhanson/oauth2orize)
22 | [](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 |
--------------------------------------------------------------------------------