Files
vnctalk-hybridauth/app/app.js
T
2018-11-08 04:35:49 +01:00

257 lines
9.7 KiB
JavaScript

var logger = require('morgan');
var moment = require('moment');
var md5 = require('md5');
var jwt = require('jsonwebtoken');
var express = require('express');
require('express-async-errors');
var bodyParser = require('body-parser');
var request = require('request');
var Pool = require('pg-pool');
var LDAP = require('ldapjs');
var app = express();
var env = process.env.NODE_ENV || 'development';
var config = require('../config/vnc-hybrid-authenticator.js')[env];
console.log(moment().format("LTS") + ' Using configuration', config);
var servicePort = config.servicePort;
var ldap;
ldap = LDAP.createClient({
url: config.ldap.ldapUri, // string
// do not wait longer than 1 minute for ldap connect
connectTimeout: 60 * 1000, // Milliseconds client should wait before timing out on TCP connections (Default: OS default)
reconnect: {
initialDelay: 200,
maxDelay: 1000,
failAfter: 10
}
});
// these options are applied in search methods
var base = config.ldap.searchBase; // default base for all future searches
//filter: config.ldap.filter, // default filter for all future searches
var scope = 'sub'; // scope: LDAP.SUBTREE
var dbpool = new Pool({
database: config.database.name,
user: config.database.user,
password: config.database.pass,
host: config.database.host,
port: config.database.port,
ssl: true,
max: 20, // set pool max size to 20
min: 4, // set min pool size to 4
idleTimeoutMillis: config.database.idleTimeoutMillis, // close idle clients after 1 second
connectionTimeoutMillis: config.database.connectionTimeoutMillis, // return an error after 1 second if connection could not be established
})
app.use(bodyParser.json()); // for parsing application/json
app.use(logger('dev'));
function IsValidLDAPUser(username) {
return new Promise( function (resolve, reject) {
var uid = username.split("@")[0];
var domain = username.split("@")[1];
var dcstring = "ou=people,dc=";
var dcstrings = domain.split(".");
if (dcstrings.length > 0) {
dcstring += dcstrings.join(",dc=");
}
var search_options = { scope : 'sub' };
var _filter = '(&(objectClass=inetOrgPerson)';
if (config.ldap.ldapType === 'zimbra') {
_filter += '(!(zimbraIsSystemResource=TRUE))(!(zimbraIsSystemAccount=TRUE))';
_filter += '(!(objectClass=zimbraDistributionList))(!(objectClass=zimbraCalendarResource))';
_filter += '(&(zimbraAccountStatus=active))(&(zimbraMailStatus=enabled))';
_filter += '(|(uid=' + uid + ')(mail=' + username + ')))';
search_options.filter = _filter;
// search_options.attributes = ['uid', 'mail', 'givenName', 'sn', 'displayName', 'zimbraAccountStatus'];
search_options.attributes = ['uid', 'mail', 'zimbraAccountStatus'];
} else if (config.ldap.ldapType === 'MS-AD') {
_filter += '(|(sAMAccountName=' + uid + ')(mail=' + username + ')))';
search_options.filter = _filter;
search_options.attributes = ['sAMAccountName', 'mail', 'givenName', 's', 'displayName' ];
} else {
console.error(moment().format("LTS") + " [profile ldap] unsupported config.ldap.ldapType : '"+config.ldap.ldapType+"'");
resolve(false);
}
console.log(moment().format("LTS") + ' ldap filter: ', _filter);
ldap.bind(
config.ldap.bindDn,
config.ldap.bindPassword,
err => {
if (err) {
console.error(moment().format("LTS") + " [profile ldap.bind]", err);
resolve(false);
} else {
var resultEntries = [];
console.log(moment().format("LTS") + ' [profile ldap.search] dcstring:', dcstring);
ldap.search(dcstring, search_options, (error, searchResult) => {
if (error) {
console.error(moment().format("LTS") + " [profile ldap.search error] ", error);
resolve(false);
}
searchResult.on('searchEntry' , e => {
var resultEntry = {};
for (e2 of e.attributes) {
resultEntry[e2.type == "sAMAccountName" ? "uid" : e2.type] = e2.vals;
}
console.log(moment().format("LTS") + ' [profile ldap.search] entry:', resultEntry);
resultEntries.push(resultEntry);
});
searchResult.on('searchReference' , ref => console.log(moment().format("LTS") + ' [profile ldap.search] referral: ' + ref));
searchResult.on('error' , err => { console.error(moment().format("LTS") + ' [profile ldap.search] on error: ' + err); resolve(false);})
searchResult.on('end' , status =>
{
console.log('[search-ldap.end] status=' + status.status + " ("+status.errorMessage+")", resultEntries.length+ " entries");
if (resultEntries.length === 1) {
console.log(moment().format("LTS") + " [valid LDAP user] ", username);
resolve(true);
} else {
console.log(moment().format("LTS") + " [no valid LDAP user] - sending 401");
resolve(false);
}
});
});
}
}
);
});
}
function IsValidLDAPAuth(username, password) {
return new Promise( function (resolve, reject) {
var uid = username.split("@")[0];
var domain = username.split("@")[1];
var dcstring = "ou=people,dc=";
var dcstrings = domain.split(".");
if (dcstrings.length > 0) {
dcstring += dcstrings.join(",dc=");
}
var search_options = { scope : 'sub' };
var uiddn = "";
if (config.ldap.ldapType === 'zimbra') {
uiddn = "uid=" + uid + "," + dcstring;
} else if (config.ldap.ldapType === 'MS-AD') {
uiddn = "sAMAccountName=" + uid + "," + dcstring;
} else {
console.error(moment().format("LTS") + " [profile ldap] unsupported config.ldap.ldapType : '"+config.ldap.ldapType+"'");
resolve(false);
}
ldap.bind( uiddn, password,
err => {
if (err) {
console.error(moment().format("LTS") + " [user ldap.bind] fail ", err);
resolve(false);
} else {
console.error(moment().format("LTS") + " [user ldap.bind]", err);
resolve(true);
}
}
);
});
}
function IsValidOldSecret(username, password) {
try {
var secretTimeseed = password.split("-")[0];
var oldHash = password.split("-")[1];
var OldYear = secretTimeseed.substring(4,8);
var OldDay = secretTimeseed.substring(0,2);
var OldMonth = secretTimeseed.substring(2,4);
var OldHour = secretTimeseed.substring(8,10);
var oldMoment = moment.utc(OldYear + "-" + OldMonth + "-" + OldDay + " " + OldHour).valueOf();
var nowMoment = moment.utc().valueOf();
if (oldMoment >= nowMoment) {
// OldSecretTest is still within valid time Timeseed as moment
var uid=username.split("@")[0].toLowerCase();
var mangledarray = [];
for (i = 0; i < uid.length; i++) {
mangledarray.push(uid.charCodeAt(i) + 1);
}
var mangledusername=mangledarray.join('');
var testSecret = secretTimeseed + mangledusername + config.xmppToken;
var testdigest = md5(testSecret);
var testhash = testdigest;
while (testhash.length < 32) {
testhash = '0' + testhash;
}
if (oldHash === testhash) {
console.log(moment().format("LTS") + " [OldSecretTest] oldSecret valid for ", username);
return true;
} else {
console.log(moment().format("LTS") + " [OldSecretTest] oldSecret invalid for ", username);
return false;
}
} else {
console.log(moment().format("LTS") + " [OldSecretTest] oldSecret expired for ", username);
return false;
}
} catch (e) {
console.error(moment().format("LTS") + " [OldSecretTest] error: ", e);
return false;
}
}
function IsValidJWT(username,password) {
try {
console.log("decodePlain: ", jwt.decode(password));
var decodedToken = jwt.verify(password, "ocu8saithoYa2teeb2ahTh9fee4is7ai");
console.log("decodedJWT: ", decodedToken);
return true;
} catch (e) {
console.error(moment().format("LTS") + " [JWTValidation] error: ", e);
return false;
}
}
app.get('/', async function (req, res) {
if (req.headers.authorization && req.headers.authorization.startsWith("Basic")) {
try {
var b64input = req.headers.authorization.split(" ")[1];
var decodedInput = new Buffer(b64input, 'base64').toString('ascii');
var username = decodedInput.split(":")[0];
var password = decodedInput.split(":")[1];
let validLdapUser = await IsValidLDAPUser(username);
console.log(moment().format("LTS") + ' username ' + username +' - validInLDAP: ', validLdapUser);
console.log(moment().format("LTS") + ' username ' + username +' - password: ', password);
console.log(moment().format("LTS") + ' username ' + username +' - indesxforOldToken: ', password.indexOf("-"));
if (validLdapUser === true) {
let validLdapAuth = await IsValidLDAPAuth(username, password);
if (validLdapUser && (validLdapAuth || IsValidOldSecret(username, password) || IsValidJWT(username, password))) {
res.status(200).json(null);
} else {
res.status(401).json(null);
}
} else {
res.status(401).json(null);
}
// res.status(200).json(null);
} catch (e) {
console.log(moment().format("LTS") + ' Error: ', e);
}
} else {
console.log(moment().format("LTS") + ' no auth header');
res.status(401).json(null);
}
});
var server = app.listen(servicePort, function () {
var host = server.address().address;
var port = server.address().port;
console.log(moment().format("LTS") + ' Service endpoint is http://%s:%s/notify', host, port);
console.log(moment().format("LTS") + ' config', config);
});