Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## 4.0.0

- bump minimal node to 16
- upgrade @node-oauth/oauth2-server to 5.1.0
- drop bluebird dependency
- upgrade all deps / dev-deps
- refactor all code to minimum es6
- use native async/await


## 3.0.0
- use @node-oauth/oauth2-server
- update all dependencies to latest
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2015 - Today Seegno and contributors
Copyright (c) 2015 - Today Node-OAuth contributors; Formerly: Seegno and contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
26 changes: 19 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ Complete, compliant and well tested module for implementing an OAuth2 Server/Pro

[![Tests](https://github.com/node-oauth/express-oauth-server/actions/workflows/tests.yml/badge.svg)](https://github.com/node-oauth/express-oauth-server/actions/workflows/tests.yml)
[![CodeQL](https://github.com/node-oauth/express-oauth-server/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/node-oauth/express-oauth-server/actions/workflows/github-code-scanning/codeql)
[![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active)
[![npm Version](https://img.shields.io/npm/v/@node-oauth/express-oauth-server?label=version)](https://www.npmjs.com/package/@node-oauth/oauth2-server)
[![npm Downloads/Week](https://img.shields.io/npm/dw/@node-oauth/express-oauth-server)](https://www.npmjs.com/package/@node-oauth/oauth2-server)
![GitHub](https://img.shields.io/github/license/node-oauth/express-oauth-server)


Expand All @@ -24,11 +27,11 @@ expect the request body to be parsed already.
The following example uses `body-parser` but you may opt for an alternative library.

```js
var bodyParser = require('body-parser');
var express = require('express');
var OAuthServer = require('@node-oauth/express-oauth-server');
const bodyParser = require('body-parser');
const express = require('express');
const OAuthServer = require('@node-oauth/express-oauth-server');

var app = express();
const app = express();

app.oauth = new OAuthServer({
model: {}, // See https://github.com/node-oauth/node-oauth2-server for specification
Expand All @@ -47,19 +50,24 @@ app.listen(3000);

## Options

> Note: The following options **extend** the default options from `@node-oauth/oauth2-sever`!
> You can read more about all possible options in the
> [@node-oauth/oauth2-sever documentation](https://node-oauthoauth2-server.readthedocs.io/en/master/api/oauth2-server.html)

```
var options = {
const options = {
useErrorHandler: false,
continueMiddleware: false,
}
```
* `useErrorHandler`

- `useErrorHandler`
(_type: boolean_ default: false)

If false, an error response will be rendered by this component.
Set this value to true to allow your own express error handler to handle the error.

* `continueMiddleware`
- `continueMiddleware`
(_type: boolean default: false_)

The `authorize()` and `token()` middlewares will both render their
Expand All @@ -69,3 +77,7 @@ var options = {
**Note:** You cannot modify the response since the headers have already been sent.

`authenticate()` does not modify the response and will always call next()

## License

MIT, see
208 changes: 135 additions & 73 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,144 +1,206 @@
/**
* Module dependencies.
*/
const NodeOAuthServer = require('@node-oauth/oauth2-server');
const { Request, Response } = require('@node-oauth/oauth2-server');

const InvalidArgumentError = require('@node-oauth/oauth2-server/lib/errors/invalid-argument-error');
const NodeOAuthServer = require('@node-oauth/oauth2-server');
const Request = require('@node-oauth/oauth2-server').Request;
const Response = require('@node-oauth/oauth2-server').Response;
const UnauthorizedRequestError = require('@node-oauth/oauth2-server/lib/errors/unauthorized-request-error');

/**
* Complete, compliant and well tested express wrapper for @node-oauth/oauth2-server in node.js.
* The module provides two middlewares - one for granting tokens and another to authorize them.
* `@node-oauth/express-oauth-server` and, consequently `@node-oauth/oauth2-server`,
* expect the request body to be parsed already.
* The following example uses `body-parser` but you may opt for an alternative library.
*
* @example
* const bodyParser = require('body-parser');
* const express = require('express');
* const OAuthServer = require('@node-oauth/express-oauth-server');
*
* const app = express();
*
* app.oauth = new OAuthServer({
* model: {}, // See https://github.com/node-oauth/node-oauth2-server for specification
* });
*
* app.use(bodyParser.json());
* app.use(bodyParser.urlencoded({ extended: false }));
* app.use(app.oauth.authorize());
*
* app.use(function(req, res) {
* res.send('Secret area');
* });
*
* app.listen(3000);
*/
class ExpressOAuthServer {
constructor(options) {
this.options = options || {};

if (!this.options.model) {
/**
* Creates a new OAuth2 server that will be bound to this class' middlewares.
* Constructor takes several options as arguments.
* The following describes only options, specific to this module.
* For all other options, please read the docs from `@node-oauth/oauth2-server`:
* @see https://node-oauthoauth2-server.readthedocs.io/en/master/api/oauth2-server.html
* @constructor
* @param options {object=} optional options
* @param options.useErrorHandler {boolean=} If false, an error response will be rendered by this component.
* Set this value to true to allow your own express error handler to handle the error.
* @param options.continueMiddleware {boolean=} The `authorize()` and `token()` middlewares will both render their
* result to the response and end the pipeline.
* next() will only be called if this is set to true.
* **Note:** You cannot modify the response since the headers have already been sent.
* `authenticate()` does not modify the response and will always call next()
*/
constructor(options = {}) {
if (!options.model) {
throw new InvalidArgumentError('Missing parameter: `model`');
}
this.useErrorHandler = this.options.useErrorHandler === true;
this.continueMiddleware = this.options.continueMiddleware === true;

delete this.options.useErrorHandler;
delete this.options.continueMiddleware;
this.useErrorHandler = !!options.useErrorHandler;
delete options.useErrorHandler;

this.continueMiddleware = !!options.continueMiddleware;
delete options.continueMiddleware;

this.server = new NodeOAuthServer(this.options);
this.server = new NodeOAuthServer(options);
}

/**
* Authentication Middleware.
*
* Returns a middleware that will validate a token.
*
* (See: https://tools.ietf.org/html/rfc6749#section-7)
* @param options {object=} will be passed to the authenticate-handler as options, see linked docs
* @see https://node-oauthoauth2-server.readthedocs.io/en/master/api/oauth2-server.html#authenticate-request-response-options
* @see: https://tools.ietf.org/html/rfc6749#section-7
* @return {function(req, res, next):Promise.<Object>}
*/
authenticate(options) {
return async (req, res, next) => {
const fn = async function(req, res, next) {
const request = new Request(req);
const response = new Response(res);
let token;

let token

try {
token = await this.server.authenticate(request, response, options);
} catch (err) {
this._handleError(res, null, err, next);
return;
} catch (e) {
return handleError.call(this, e, req, res, null, next);
}

res.locals.oauth = { token };
return next();
}
next();
};

return fn.bind(this);
}

/**
* Authorization Middleware.
*
* Returns a middleware that will authorize a client to request tokens.
*
* (See: https://tools.ietf.org/html/rfc6749#section-3.1)
* @param options {object=} will be passed to the authorize-handler as options, see linked docs
* @see https://node-oauthoauth2-server.readthedocs.io/en/master/api/oauth2-server.html#authorize-request-response-options
* @see: https://tools.ietf.org/html/rfc6749#section-3.1
* @return {function(req, res, next):Promise.<Object>}
*/
authorize(options) {
return async (req, res, next) => {
const fn = async function(req, res, next) {
const request = new Request(req);
const response = new Response(res);
let code;

let code

try {
code = await this.server.authorize(request, response, options);
} catch (err) {
this._handleError(res, response, err, next);
return;
} catch (e) {
return handleError.call(this, e, req, res, response, next);
}

res.locals.oauth = { code };
if (this.continueMiddleware) {
next();
}
return this._handleResponse(req, res, response);
}
}

return handleResponse.call(this, req, res, response);
};

return fn.bind(this);
}

/**
* Authorization Middleware.
*
* Returns a middleware that will authorize a client to request tokens.
* Grant Middleware.
* Returns middleware that will grant tokens to valid requests.
*
* (See: https://tools.ietf.org/html/rfc6749#section-3.1)
* @param options {object=} will be passed to the token-handler as options, see linked docs
* @see https://node-oauthoauth2-server.readthedocs.io/en/master/api/oauth2-server.html#token-request-response-options
* @see: https://tools.ietf.org/html/rfc6749#section-3.2
* @return {function(req, res, next):Promise.<Object>}
*/
token(options) {
return async (req, res, next) => {
const fn = async function(req, res, next) {
const request = new Request(req);
const response = new Response(res);
let token;

let token

try {
token = await this.server.token(request, response, options);
} catch (err) {
this._handleError(res, response, err, next);
return;
} catch (e) {
return handleError.call(this, e, req, res, response, next);
}

res.locals.oauth = { token };
if (this.continueMiddleware) {
next();
}
return this._handleResponse(req, res, response);
}
}

/**
* Grant Middleware.
*
* Returns middleware that will grant tokens to valid requests.
*
* (See: https://tools.ietf.org/html/rfc6749#section-3.2)
*/
_handleResponse(req, res, oauthResponse) {
if (oauthResponse.status === 302) {
const location = oauthResponse.headers.location;
delete oauthResponse.headers.location;
res.set(oauthResponse.headers);
res.redirect(location);
return;
}
res.set(oauthResponse.headers);
res.status(oauthResponse.status).send(oauthResponse.body);
return handleResponse.call(this, req, res, response);
};

return fn.bind(this);
}
}

/**
* Handles errors depending on the options of `this.useErrorHandler`.
* Either calls `next()` with the error (so the application can handle it), or returns immediately a response with the error.
*/
_handleError(res, oauthResponse, error, next) {
if (this.useErrorHandler) {
return next(error);
}
/**
* Handle response.
*/
const handleResponse = function(req, res, response) {
if (response.status === 302) {
const location = response.headers.location;
delete response.headers.location;
res.set(response.headers);
res.redirect(location);
} else {
res.set(response.headers);
res.status(response.status).send(response.body);
}
};

if (oauthResponse) {
res.set(oauthResponse.headers);
/**
* Handle error.
*/
const handleError = function(e, req, res, response, next) {
if (this.useErrorHandler === true) {
next(e);
} else {
if (response) {
res.set(response.headers);
}

res.status(error.code || 500);
res.status(e.code);

if (error instanceof UnauthorizedRequestError) {
if (e instanceof UnauthorizedRequestError) {
return res.send();
}

return res.send({ error: error.name, error_description: error.message });
res.send({ error: e.name, error_description: e.message });
}
}
};

/**
* Export constructor.
*/

module.exports = ExpressOAuthServer;
Loading