summaryrefslogtreecommitdiff
path: root/src/node/hooks/express/webaccess.js
blob: 190021a3eba52141789456ec3b5b914025abe522 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
var express = require('express');
var log4js = require('log4js');
var httpLogger = log4js.getLogger("http");
var settings = require('../../utils/Settings');
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
var ueberStore = require('../../db/SessionStore');
var stats = require('ep_etherpad-lite/node/stats');
var sessionModule = require('express-session');
var cookieParser = require('cookie-parser');

//checks for basic http auth
exports.basicAuth = function (req, res, next) {
  var hookResultMangle = function (cb) {
    return function (err, data) {
      return cb(!err && data.length && data[0]);
    }
  }

  var authorize = function (cb) {
    // Do not require auth for static paths and the API...this could be a bit brittle
    if (req.path.match(/^\/(static|javascripts|pluginfw|api)/)) return cb(true);

    if (req.path.indexOf('/admin') != 0) {
      if (!settings.requireAuthentication) return cb(true);
      if (!settings.requireAuthorization && req.session && req.session.user) return cb(true);
    }

    if (req.session && req.session.user && req.session.user.is_admin) return cb(true);

    hooks.aCallFirst("authorize", {req: req, res:res, next:next, resource: req.path}, hookResultMangle(cb));
  }

  var authenticate = function (cb) {
    // If auth headers are present use them to authenticate...
    if (req.headers.authorization && req.headers.authorization.search('Basic ') === 0) {
      var userpass = new Buffer(req.headers.authorization.split(' ')[1], 'base64').toString().split(":")
      var username = userpass.shift();
      var password = userpass.join(':');

      if (settings.users[username] != undefined && settings.users[username].password == password) {
        settings.users[username].username = username;
        req.session.user = settings.users[username];
        return cb(true);
      }
      return hooks.aCallFirst("authenticate", {req: req, res:res, next:next, username: username, password: password}, hookResultMangle(cb));
    }
    hooks.aCallFirst("authenticate", {req: req, res:res, next:next}, hookResultMangle(cb));
  }


  /* Authentication OR authorization failed. */
  var failure = function () {
    return hooks.aCallFirst("authFailure", {req: req, res:res, next:next}, hookResultMangle(function (ok) {
    if (ok) return;
      /* No plugin handler for invalid auth. Return Auth required
       * Headers, delayed for 1 second, if authentication failed
       * before. */
      res.header('WWW-Authenticate', 'Basic realm="Protected Area"');
      if (req.headers.authorization) {
        setTimeout(function () {
          res.status(401).send('Authentication required');
        }, 1000);
      } else {
        res.status(401).send('Authentication required');
      }
    }));
  }


  /* This is the actual authentication/authorization hoop. It is done in four steps:

     1) Try to just access the thing
     2) If not allowed using whatever creds are in the current session already, try to authenticate
     3) If authentication using already supplied credentials succeeds, try to access the thing again
     4) If all els fails, give the user a 401 to request new credentials

     Note that the process could stop already in step 3 with a redirect to login page.

  */

  authorize(function (ok) {
    if (ok) return next();
    authenticate(function (ok) {
      if (!ok) return failure();
      authorize(function (ok) {
        if (ok) return next();
        failure();
      });
    });
  });
}

exports.secret = null;

exports.expressConfigure = function (hook_name, args, cb) {
  // Measure response time
  args.app.use(function(req, res, next) {
    var stopWatch = stats.timer('httpRequests').start();
    var sendFn = res.send
    res.send = function() {
      stopWatch.end()
      sendFn.apply(res, arguments)
    }
    next()
  })

  // If the log level specified in the config file is WARN or ERROR the application server never starts listening to requests as reported in issue #158.
  // Not installing the log4js connect logger when the log level has a higher severity than INFO since it would not log at that level anyway.
  if (!(settings.loglevel === "WARN" || settings.loglevel == "ERROR"))
    args.app.use(log4js.connectLogger(httpLogger, { level: log4js.levels.DEBUG, format: ':status, :method :url'}));

  /* Do not let express create the session, so that we can retain a
   * reference to it for socket.io to use. Also, set the key (cookie
   * name) to a javascript identifier compatible string. Makes code
   * handling it cleaner :) */

  if (!exports.sessionStore) {
    exports.sessionStore = new ueberStore();
    exports.secret = settings.sessionKey;
  }

  args.app.sessionStore = exports.sessionStore;
  args.app.use(sessionModule({secret: exports.secret, store: args.app.sessionStore, resave: true, saveUninitialized: true, name: 'express_sid', proxy: true, cookie: { secure: !!settings.ssl }}));

  args.app.use(cookieParser(settings.sessionKey, {}));

  args.app.use(exports.basicAuth);
}