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/. */
Lloyd Hilaiel
committed
/* this is a small standalone abstraction which lets scripts be
* browserid WSAPI clients. It handles CSRF token fetching and
* extraction/resending of cookies. It also allows one to have
* any number of "client contexts" which are just objects, and lets
* you simulated different simultaneous sessions.
*/
const
http = require('http'),
https = require('https'),
Lloyd Hilaiel
committed
url = require('url'),
querystring = require('querystring');
Lloyd Hilaiel
committed
function injectCookies(ctx, headers) {
if (ctx.cookieJar && Object.keys(ctx.cookieJar).length) {
headers['Cookie'] = "";
for (var k in ctx.cookieJar) {
headers['Cookie'] += k + "=" + ctx.cookieJar[k];
}
}
Lloyd Hilaiel
committed
}
Lloyd Hilaiel
committed
function extractCookies(ctx, res) {
if (ctx.cookieJar === undefined) ctx.cookieJar = {};
if (res.headers['set-cookie']) {
res.headers['set-cookie'].forEach(function(cookie) {
var m = /^([^;]+)(?:;.*)$/.exec(cookie);
if (m) {
var x = m[1].split('=');
ctx.cookieJar[x[0]] = x[1];
}
});
}
}
exports.clearCookies = function(ctx) {
if (ctx && ctx.cookieJar) delete ctx.cookieJar;
if (ctx && ctx.session) delete ctx.session;
};
Lloyd Hilaiel
committed
exports.getCookie = function(ctx, which) {
Zachary Carter
committed
if (typeof which === 'string') which = new RegExp('/^' + which + '$/');
Lloyd Hilaiel
committed
var cookieNames = Object.keys(ctx.cookieJar);
for (var i = 0; i < cookieNames.length; i++) {
if (which.test(cookieNames[i])) return ctx.cookieJar[cookieNames[i]];
}
return null;
};
exports.injectCookies = injectCookies;
Lloyd Hilaiel
committed
exports.get = function(cfg, path, context, getArgs, cb) {
Lloyd Hilaiel
committed
// parse the server URL (cfg.browserid)
var uObj;
var meth;
try {
uObj = url.parse(cfg.browserid);
meth = uObj.protocol === 'http:' ? http : https;
} catch(e) {
Lloyd Hilaiel
committed
cb("can't parse url: " + e);
Lloyd Hilaiel
committed
return;
}
Lloyd Hilaiel
committed
var headers = { };
injectCookies(context, headers);
Lloyd Hilaiel
committed
if (typeof getArgs === 'object')
path += "?" + querystring.stringify(getArgs);
Lloyd Hilaiel
committed
meth.get({
host: uObj.hostname,
port: uObj.port,
path: path,
Lloyd Hilaiel
committed
headers: headers,
agent: false // disable node.js connection pooling
Lloyd Hilaiel
committed
}, function(res) {
extractCookies(context, res);
var body = '';
res.on('data', function(chunk) { body += chunk; })
.on('end', function() {
Lloyd Hilaiel
committed
cb(null, {code: res.statusCode, headers: res.headers, body: body});
Lloyd Hilaiel
committed
});
}).on('error', function (e) {
Lloyd Hilaiel
committed
cb(e);
Lloyd Hilaiel
committed
});
};
function withCSRF(cfg, context, cb) {
Lloyd Hilaiel
committed
if (context.session && context.session.csrf_token) cb(null, context.session.csrf_token);
Lloyd Hilaiel
committed
else {
Lloyd Hilaiel
committed
exports.get(cfg, '/wsapi/session_context', context, undefined, function(err, r) {
if (err) return cb(err);
Lloyd Hilaiel
committed
try {
if (r.code !== 200)
return cb({what: "http error", resp: r}); // report first error
context.session = JSON.parse(r.body);
Lloyd Hilaiel
committed
context.sessionStartedAt = new Date().getTime();
Lloyd Hilaiel
committed
cb(null, context.session.csrf_token);
Lloyd Hilaiel
committed
} catch(e) {
console.log('error getting csrf token: ', e);
Lloyd Hilaiel
committed
cb(e);
Lloyd Hilaiel
committed
}
Lloyd Hilaiel
committed
});
}
}
exports.post = function(cfg, path, context, postArgs, cb) {
Lloyd Hilaiel
committed
withCSRF(cfg, context, function(err, csrf) {
if (err) {
if (err.what == "http error") {
// let the session_context HTTP return code speak for the overall
// POST
return cb(null, err.resp);
}
return cb(err);
}
Lloyd Hilaiel
committed
Lloyd Hilaiel
committed
// parse the server URL (cfg.browserid)
var uObj;
var meth;
Zachary Carter
committed
var body;
Lloyd Hilaiel
committed
try {
uObj = url.parse(cfg.browserid);
meth = uObj.protocol === 'http:' ? http : https;
} catch(e) {
Lloyd Hilaiel
committed
cb("can't parse url: " + e);
Lloyd Hilaiel
committed
return;
}
var headers = {
'Content-Type': 'application/json'
Lloyd Hilaiel
committed
};
injectCookies(context, headers);
if (typeof postArgs === 'object') {
Lloyd Hilaiel
committed
postArgs['csrf'] = csrf;
body = JSON.stringify(postArgs);
headers['Content-Length'] = Buffer.byteLength(body);
Lloyd Hilaiel
committed
}
var req = meth.request({
host: uObj.hostname,
port: uObj.port,
path: path,
headers: headers,
Lloyd Hilaiel
committed
method: "POST",
agent: false // disable node.js connection pooling
Lloyd Hilaiel
committed
}, function(res) {
extractCookies(context, res);
var body = '';
res.on('data', function(chunk) { body += chunk; })
.on('end', function() {
Lloyd Hilaiel
committed
cb(null, {code: res.statusCode, headers: res.headers, body: body});
Lloyd Hilaiel
committed
});
}).on('error', function (e) {
Lloyd Hilaiel
committed
cb(e);
Lloyd Hilaiel
committed
});
req.write(body);
req.end();
});
};