OpenVeo server API for plugins

API Docs for: 7.0.0
Show:

File: lib/passport/strategies/cas/CasStrategy.js

'use strict';

/**
 * Defines a passport cas strategy.
 *
 * @module passport
 */

const url = require('url');
const Strategy = require('passport-strategy');

class CasStrategy extends Strategy {

  /**
   * Defines a passport cas strategy to authenticate requests using a cas server.
   *
   * @example
   *     e.g. Configuration example
   *     // {
   *     //   "service": "https://my-application-service-host", // Application service
   *     //   "url": "https://my-cas-server-host:8443/cas", // CAS server url
   *     //   "version": "3", // CAS protocol version (could be 1, 2, 3)
   *     //   "certificate": "/home/test/cas.crt" // CAS full chain certificate
   *     // }
   *
   * @class CasStrategy
   * @constructor
   * @extends Strategy
   * @param {Object} options The list of cas strategy options
   * @param {String} options.service The service to use to authenticate to the CAS server
   * @param {String} options.version The version of the CAS server
   * @param {String} options.url The url of the CAS server
   * @param {String} options.certificate The absolute path to the CAS server certificate
   * @param {Function} verify Function to call to validate user as returnd by CAS
   */
  constructor(options, verify) {
    super();

    if (!options)
      throw new Error('Missing passport cas strategy options');

    if (!options.service)
      throw new Error('Missing cas service for cas strategy');

    const version = options.version ? options.version : '3';
    let cas = null;

    try {
      const CAS = require(`./CAS${version}.js`);
      cas = new CAS(options);
    } catch (error) {
      throw new Error(`This version of cas is not implemented : ${error.message}`);
    }

    Object.defineProperties(this, {

      /**
       * Passport cas strategy name.
       *
       * @property cas
       * @type String
       * @default "cas"
       * @final
       */
      name: {
        value: 'cas'
      },

      /**
       * Application service registered in CAS.
       *
       * @property service
       * @type String
       * @final
       */
      service: {
        value: options.service
      },

      /**
       * CAS protocol version.
       *
       * @property version
       * @type String
       * @final
       */
      version: {
        value: version
      },

      /**
       * CAS client implementation.
       *
       * @property cas
       * @type CAS
       * @final
       */
      cas: {
        value: cas
      },

      /**
       * Passport verify function to call to validate user returned by CAS.
       *
       * @property verify
       * @type Function
       * @final
       */
      verify: {
        value: verify
      },

      /**
       * URI to return to after logging out.
       *
       * @property logoutUri
       * @type Function
       * @final
       */
      logoutUri: {
        value: options.logoutUri
      }

    });

  }

  /**
   * Authenticates a request using cas.
   *
   * @method authenticate
   * @async
   * @param {Object} request The express authenticate request
   * @param {Object} options Passport authenticate options such as redirects
   */
  authenticate(request, options) {
    if (!request._passport) return this.error(new Error('passport.initialize() middleware not in use'));

    const self = this;

    // Parse request url and cas service urls
    const urlChunks = url.parse(request.originalUrl || request.url, true);
    const serviceChunks = url.parse(this.service);

    if (request.query && request.query.ticket) {

      // Got a ticket to validate

      // Interrogate cas to validate the ticket
      this.cas.validateTicket(url.format({
        protocol: serviceChunks.protocol,
        host: serviceChunks.host,
        pathname: urlChunks.pathname
      }), request.query.ticket).then((user) => {

        // Offer the possibility to verify the user returned by CAS server
        // before considering him authenticated
        if (self.verify) {
          return self.verify(user, (error, verifiedUser, info) => {
            if (error) return self.error(error);
            if (!verifiedUser) return self.fail(info);
            return self.success(verifiedUser, info);
          });
        } else
          return self.success(user);
      }).catch((error) => {
        const message = `Authentication failed with message : "${error.message}"`;
        process.logger.error(message);
        return self.fail(message);
      });

    } else {

      // No ticket, redirect to cas server login page

      // Build login url with service registered in cas server
      const loginUrl = this.cas.getUrl() + url.format({
        pathname: this.cas.getLoginUri(),
        query: {
          service: url.format({
            protocol: serviceChunks.protocol,
            host: serviceChunks.host,
            pathname: urlChunks.pathname,
            query: urlChunks.query
          })
        }
      });

      // Redirect to cas login page
      this.redirect(loginUrl, 307);
    }
  }

  /**
   * Logouts from cas.
   *
   * @method logout
   * @param {Object} request The express logout request
   * @param {Object} request The express response
   */
  logout(request, response) {
    const serviceChunks = url.parse(this.service);
    const service = url.format({
      protocol: serviceChunks.protocol,
      host: serviceChunks.host
    });

    // Build login url with service registered in cas server
    const logoutUrl = this.cas.getUrl() + url.format({
      pathname: this.cas.getLogoutUri(),
      query: {
        service,
        url: `${service}/${this.logoutUri}`
      }
    });

    // Logout from cas by redirecting to cas logout url
    response.statusCode = 307;
    response.setHeader('Location', logoutUrl);
    response.setHeader('Content-Length', '0');
    response.end();
  }

}

module.exports = CasStrategy;