Skip to content
Snippets Groups Projects
views.js 9.17 KiB
Newer Older
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const
metrics = require('../metrics.js'),
url = require('url'),
logger = require('../logging.js').logger,
fs = require('fs'),
config = require('../configuration.js'),
und = require('underscore'),
httputils = require('../httputils.js'),
secrets = require('../secrets'),
version = require('../version');
// the underbar decorator to allow getext to extract strings
function _(str) { return str; }

// all templated content, redirects, and renames are handled here.
// anything that is not an api, and not static
const
path = require('path');

const VIEW_PATH = path.join(__dirname, "..", "..", "resources", "views");

// none of our views include dynamic data.  all of them should be served
// with reasonable cache headers.  This wrapper around rendering handles
// cache headers maximally leveraging the same logic that connect uses
// issue #910
function renderCachableView(req, res, template, options) {
  if (config.get('env') !== 'local') {
    // allow caching, but require revalidation via ETag
    res.etagify();
    res.setHeader('Cache-Control', 'public, max-age=0');
  } else {
    // disable all caching for local dev
    res.setHeader('Cache-Control', 'no-store');
  }
  res.setHeader('Date', new Date().toUTCString());
  res.setHeader('Vary', 'Accept-Encoding,Accept-Language');
  res.setHeader('Content-Type', 'text/html; charset=utf8');

  options.enable_development_menu = config.get('enable_development_menu');

  // The real version number is not ready until sometime after initial load,
  // until it is ready a fake randomly generated string is used. Go get
  // the real SHA whenever it is actually needed so that the randomly
  // generated SHA is not returned to the user.
  options.commit = version();

  res.render(template, options);

  // Issue#1353 This is kind of dirty, but this is our last chance
  // to fixup headers for an ETag cache hit
  // x-frame-options - Allow these to be run within a frame
  app.use(function (req, res, next) {
    if (req.path === '/communication_iframe') {
      res.removeHeader('x-frame-options');
    } else if (req.path === '/relay') {
      res.removeHeader('x-frame-options');
    }
    next();
  });

  // Caching for dynamic resources

  app.set('view options', {
    production: config.get('use_minified_resources')
  });

  app.get('/include.js', function(req, res, next) {

    if (config.get('use_minified_resources') === true) {
  });

  app.get('/include.orig.js', function(req, res, next) {
    req.url = "/include_js/include.js";
    next();
  // this should probably be an internal redirect
  // as soon as relative paths are figured out.
  app.get('/sign_in', function(req, res, next ) {
    renderCachableView(req, res, 'dialog.ejs', {
      title: _('A Better Way to Sign In'),
      layout: 'dialog_layout.ejs',
      useJavascript: true,
      production: config.get('use_minified_resources')
    });
  });

  app.get('/communication_iframe', function(req, res, next ) {
    renderCachableView(req, res, 'communication_iframe.ejs', {
      layout: false,
      production: config.get('use_minified_resources')
    });
  });

  app.get("/unsupported_dialog", function(req,res) {
    renderCachableView(req, res, 'unsupported_dialog.ejs', {
      title: _('Unsupported Browser'),
      layout: 'dialog_layout.ejs',
      useJavascript: false
    });
    renderCachableView(req, res, 'cookies_disabled.ejs', {
      title: _('Cookies Are Disabled'),
      layout: 'dialog_layout.ejs',
      useJavascript: false
    });
  // Used for a relay page for communication.
  app.get("/relay", function(req, res, next) {
    renderCachableView(req, res, 'relay.ejs', {
      layout: false,
      production: config.get('use_minified_resources')
    });
  });

  app.get("/authenticate_with_primary", function(req,res, next) {
    renderCachableView(req, res, 'authenticate_with_primary.ejs', { layout: false });
    renderCachableView(req, res, 'index.ejs', {title: _('A Better Way to Sign In'), fullpage: true});
  app.get("/idp_auth_complete", function(req, res) {
    renderCachableView(req, res, 'idp_auth_complete.ejs', {
      title: _('Sign In Complete'),
  app.get("/forgot", function(req, res) {
    renderCachableView(req, res, 'forgot.ejs', {
      title: _('Forgot Password'),
      fullpage: false,
      enable_development_menu: config.get('enable_development_menu')
    });
  app.get("/signup", function(req, res) {
    res.header('Location', '/signin');
    res.send(301);
  });

  app.get("/signin", function(req, res) {
    renderCachableView(req, res, 'signin.ejs', {title: _('Sign In'), fullpage: false});
    renderCachableView(req, res, 'about.ejs', {title: _('About'), fullpage: false});
    renderCachableView(req, res, 'tos.ejs', {title: _('Terms of Service'), fullpage: false});
    renderCachableView(req, res, 'privacy.ejs', {title: _('Privacy Policy'), fullpage: false});
  });

  app.get("/verify_email_address", function(req, res) {
    renderCachableView(req, res, 'verify_email_address.ejs', {
      title: _('Complete Registration'),
      fullpage: true,
      enable_development_menu: config.get('enable_development_menu')
    });
  // This page can be removed a couple weeks after this code ships into production,
  // we're leaving it here to not break outstanding emails
  app.get("/add_email_address", function(req,res) {
    renderCachableView(req, res, 'confirm.ejs', {title: _('Verify Email Address'), fullpage: false});

  app.get("/reset_password", function(req,res) {
    renderCachableView(req, res, 'confirm.ejs', {title: _('Reset Password')});
    renderCachableView(req, res, 'confirm.ejs', {title: _('Confirm Email')});
  // serve up testing templates.  but NOT in staging or production.  see GH-1044
  if ([ 'https://login.persona.org', 'https://login.anosrep.org' ].indexOf(config.get('public_url')) === -1) {
    // serve test.ejs to /test or /test/ or /test/index.html
    app.get(/^\/test\/(?:index.html)?$/, function (req, res) {
      res.render('test.ejs', {title: 'Mozilla Persona QUnit Test', layout: false});
    var testPath = path.join(__dirname, '..', '..', 'tests', 'i18n_test_templates');
    app.get('/i18n_test', function(req, res) {
      renderCachableView(req, res, path.join(testPath, 'i18n_test.ejs'), { layout: false, title: 'l10n testing title' });
    app.get('/i18n_fallback_test', function(req, res) {
      renderCachableView(req, res, path.join(testPath, 'i18n_fallback_test.ejs'), { layout: false, title: 'l10n testing title' });
  } else {
    // this is stage or production, explicitly disable all resources under /test
    app.get(/^\/test/, function(req, res) {
      httputils.notFound(res, "Cannot " + req.method + " " + req.url);
    "/developers" : "https://developer.mozilla.org/docs/persona"
  };

  // set up all the redirects
  // oh my watch out for scope issues on var url - closure time
  for (var url in REDIRECTS) {
    (function(from,to) {
      app.get(from, function(req, res) {
        res.redirect(to);
      });
    })(url, REDIRECTS[url]);
  }

    const publicKey = secrets.loadPublicKey();
  } catch(e){
    logger.error("can't read public key, exiting: " + e);
    process.nextTick(function() { process.exit(1); });

  // the "declaration of support" style publishing of the public key.
  // login.persona.org is a (uh, THE) secondary, it should publish its key
  // in a manner that is symmetric with how primaries do.  At present,
  // the absence of 'provisioning' and 'authentication' keys indicates
  // that this is a secondary, and verifiers should only trust
  // login.persona.org as a secondary (and anyone else they decide to for
  // whatever reason).
  app.get("/.well-known/browserid", function(req, res) {
    res.json({ 'public-key': publicKey.toSimpleObject() });

  // now for static redirects for cach busting - issue #225
  var versionRegex = /^(\/production\/[a-zA-Z\-]+)_v[a-zA-Z0-9]{7}(\.(?:css|js))$/;
  app.use(function(req, res, next) {
    var m = versionRegex.exec(req.url);
    if (m) {
      var newURL = m[1] + m[2];
      logger.debug('internal redirect of ' + req.url + ' to ' + newURL);
      req.url = newURL;
    }
    next();
  });