Source: index.js

/**
 * Module dependencies.
 */

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.
 *
 * @class
 * @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 {
  /**
   * 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 = !!options.useErrorHandler;
    delete options.useErrorHandler;

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

    this.server = new NodeOAuthServer(options);
  }

  /**
   * Authentication Middleware.
   * Returns a middleware that will validate a token.
   *
   * @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) {
    const fn = async function(req, res, next) {
      const request = new Request(req);
      const response = new Response(res);

      let token

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

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

    return fn.bind(this);
  }

  /**
   * Authorization Middleware.
   * Returns a middleware that will authorize a client to request tokens.
   *
   * @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) {
    const fn = async function(req, res, next) {
      const request = new Request(req);
      const response = new Response(res);

      let code

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

      res.locals.oauth = { code };
      if (this.continueMiddleware) {
        next();
      }

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

    return fn.bind(this);
  }

  /**
   * Grant Middleware.
   * Returns middleware that will grant tokens to valid requests.
   *
   * @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) {
    const fn = async function(req, res, next) {
      const request = new Request(req);
      const response = new Response(res);

      let token

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

      res.locals.oauth = { token };
      if (this.continueMiddleware) {
        next();
      }

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

    return fn.bind(this);
  }
}

/**
 * Handle response.
 * @private
 */
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);
  }
};

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

    res.status(e.code);

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

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

/**
 * Export constructor.
 * @private
 */

module.exports = ExpressOAuthServer;