diff --git a/bin/browserid b/bin/browserid index c7bc941cf8fd7f75b04541e9220417093582b9f2..225526ab27a818edbc3f7545e0f216b088c5573f 100755 --- a/bin/browserid +++ b/bin/browserid @@ -25,8 +25,7 @@ heartbeat = require('../lib/heartbeat.js'), metrics = require('../lib/metrics.js'), logger = require('../lib/logging.js').logger, forward = require('../lib/http_forward').forward, -shutdown = require('../lib/shutdown'), -views = require('../lib/browserid/views.js'); +shutdown = require('../lib/shutdown'); var app = undefined; @@ -39,7 +38,13 @@ logger.info("browserid server starting up"); // #1 - Setup health check / heartbeat middleware. // This is in front of logging on purpose. see issue #537 -heartbeat.setup(app); +heartbeat.setup(app, function(cb) { + // ping the database to verify we're really healthy. + db.ping(function(e) { + if (e) logger.error("database ping error: " + e); + cb(!e); + }); +}); // #2 - logging! all requests other than __heartbeat__ are logged app.use(express.logger({ @@ -99,15 +104,6 @@ app.use(function(req, resp, next) { return next(); }); -var static_root = path.join(__dirname, "..", "resources", "static"); - -app.use(cachify.setup(assets(config.get('supported_languages')), - { - prefix: config.get('cachify_prefix'), - production: config.get('use_minified_resources'), - root: static_root - })); - // #7 - perform response substitution to support local/dev/beta environments // (specifically, this replaces URLs in responses, e.g. https://login.persona.org // with https://login.anosrep.org) @@ -118,40 +114,13 @@ wsapi.setup({ forward_writes: urlparse(config.get('dbwriter_url')).validate().normalize().originOnly() }, app); -// #9 - handle views for dynamicish content -views.setup(app); - -// #10 if the BROWSERID_FAKE_VERIFICATION env var is defined, we'll include +// #9 if the BROWSERID_FAKE_VERIFICATION env var is defined, we'll include // fake_verification.js. This is used during testing only and should // never be included in a production deployment if (process.env['BROWSERID_FAKE_VERIFICATION']) { require('../lib/browserid/fake_verification.js').addVerificationWSAPI(app); } -// if nothing else has caught this request, serve static files, but ensure -// that proper vary headers are installed to prevent unwanted caching -app.use(function(req, res, next) { - res.setHeader('Vary', 'Accept-Encoding,Accept-Language'); - next(); -}); - -// add 'Access-Control-Allow-Origin' headers to static resources that will be served -// from the CDN. We explicitly allow resources served from public_url to access these. -app.use(function(req, res, next) { - res.on('header', function() { - res.setHeader("Access-Control-Allow-Origin", config.get('public_url')); - }); - next(); -}); - -// if we're not serving minified resources (local dev), then we should add -// .ejs to the mime table so it's properly substituted. issue #1875 -if (!config.get('use_minified_resources')) { - express.static.mime.types['ejs'] = 'text/html'; -} - -app.use(express.static(static_root)); - // open the databse db.open(config.get('database'), function (error) { if (error) { diff --git a/bin/router b/bin/router index 3f7f1330ddb431d5eb8b16076e0b29b68aefca26..1d333c310362f4d09ab8dd9b2873711b02a85f0d 100755 --- a/bin/router +++ b/bin/router @@ -48,10 +48,12 @@ if (!config.get('browserid_url')) { // order in which middleware will be invoked as requests are processed. // #1 - Setup health check / heartbeat middleware. +// Depends on positive health checks from browserid and static processes // This is in front of logging on purpose. see issue #537 var browserid_url = urlparse(config.get('browserid_url')).validate().normalize().originOnly(); +var static_url = urlparse(config.get('static_url')).validate().normalize().originOnly(); heartbeat.setup(app, { - dependencies: [browserid_url] + dependencies: [browserid_url, static_url] }); // #2 - logging! all requests other than __heartbeat__ are logged @@ -115,16 +117,37 @@ if (config.get('verifier_url')) { }); } -// handle /wsapi writes -wsapi.setup({ - router_mode: true, - forward_writes: urlparse(config.get('dbwriter_url')).validate().normalize().originOnly() -}, app); +// #10 if the BROWSERID_FAKE_VERIFICATION env var is defined, we'll include +// fake_verification.js. This is used during testing only and should +// never be included in a production deployment +if (process.env['BROWSERID_FAKE_VERIFICATION']) { + app.use(function(req, res, next) { + if (url.parse(req.url).pathname == '/wsapi/fake_verification') { + forward( + browserid_url+req.url, req, res, + function(err) { + if (err) { + logger.error("error forwarding request:", err); + } + }); + } else { + return next(); + } + }); +} + +// handle /wsapi reads/writes +var dbwriter_url = urlparse(config.get('dbwriter_url')).validate().normalize().originOnly(); + +wsapi.routeSetup(app, { + read_url: browserid_url, + write_url: dbwriter_url +}); -// Forward all leftover requests to browserid +//catch-all app.use(function(req, res, next) { forward( - browserid_url+req.url, req, res, + static_url+req.url, req, res, function(err) { if (err) { logger.error("error forwarding request:", err); diff --git a/bin/static b/bin/static new file mode 100755 index 0000000000000000000000000000000000000000..47af95441d6858387a0bfa09918fef5c124bdc52 --- /dev/null +++ b/bin/static @@ -0,0 +1,106 @@ +#!/usr/bin/env node + +/* 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 +fs = require('fs'), +path = require('path'), +url = require('url'), +http = require('http'), +urlparse = require('urlparse'), +express = require('express'); + +const +assets = require('../lib/static_resources').all, +cachify = require('connect-cachify'), +i18n = require('../lib/i18n.js'), +wsapi = require('../lib/wsapi.js'), +httputils = require('../lib/httputils.js'), +secrets = require('../lib/secrets.js'), +db = require('../lib/db.js'), +config = require('../lib/configuration.js'), +heartbeat = require('../lib/heartbeat.js'), +metrics = require('../lib/metrics.js'), +logger = require('../lib/logging.js').logger, +forward = require('../lib/http_forward').forward, +shutdown = require('../lib/shutdown'), +views = require('../lib/static/views.js'); + +var app = undefined; + +app = express.createServer(); + +logger.info("static starting up"); + +// Setup health check / heartbeat middleware. +// This is in front of logging on purpose. see issue #537 +heartbeat.setup(app); + +// logging! all requests other than __heartbeat__ are logged +app.use(express.logger({ + format: config.get('express_log_format'), + stream: { + write: function(x) { + logger.info(typeof x === 'string' ? x.trim() : x); + } + } +})); + +// #2.1 - localization +app.use(i18n.abide({ + supported_languages: config.get('supported_languages'), + default_lang: config.get('default_lang'), + debug_lang: config.get('debug_lang'), + locale_directory: config.get('locale_directory'), + disable_locale_check: config.get('disable_locale_check') +})); + +var statsd_config = config.get('statsd'); +if (statsd_config && statsd_config.enabled) { + logger_statsd = require("connect-logger-statsd"); + app.use(logger_statsd({ + host: statsd_config.hostname || "localhost", + port: statsd_config.port || 8125, + prefix: statsd_config.prefix || "browserid.static." + })); +} +// #4 - prevent framing of everything. content underneath that needs to be +// framed must explicitly remove the x-frame-options +app.use(function(req, resp, next) { + resp.setHeader('x-frame-options', 'DENY'); + next(); +}); + +var static_root = path.join(__dirname, "..", "resources", "static"); + +// #7 - perform response substitution to support local/dev/beta environments +// (specifically, this replaces URLs in responses, e.g. https://browserid.org +// with https://diresworb.org) +config.performSubstitution(app); + +// #9 - handle views for dynamicish content +views.setup(app); + +app.use(cachify.setup(assets(config.get('supported_languages')), + { + prefix: config.get('cachify_prefix'), + production: config.get('use_minified_resources'), + root: static_root, + })); + + +// if nothing else has caught this request, serve static files, but ensure +// that proper vary headers are installed to prevent unwanted caching +app.use(function(req, res, next) { + res.setHeader('Vary', 'Accept-Encoding,Accept-Language'); + next(); +}); + +app.use(express.static(static_root)); + +var bindTo = config.get('bind_to'); +app.listen(bindTo.port, bindTo.host, function() { + logger.info("running on http://" + app.address().address + ":" + app.address().port); +}); diff --git a/config/local.json b/config/local.json index 0aec32890f91b63109148e09f7bf85cd7877df87..ba653f4feb79cb3112b0265960ce72bd667204d4 100644 --- a/config/local.json +++ b/config/local.json @@ -4,6 +4,7 @@ "dbwriter": { "bind_to": { "port": 10004 } }, "proxy": { "bind_to": { "port": 10006 } }, "browserid": { "bind_to": { "port": 10007 } }, + "static": { "bind_to": { "port": 10010 } }, "router": { "bind_to": { "port": 10002 } }, "use_minified_resources": false, "database": { diff --git a/config/production.json b/config/production.json index 13cbcaf2ed977a1d1dc597c93a2d988335666677..4cb9c533fddfb98d9a6b2ce744f3bc7e2e67895d 100644 --- a/config/production.json +++ b/config/production.json @@ -50,7 +50,9 @@ "dbwriter_url": "http://127.0.0.1:62900", "browserid": { "bind_to": { "port": 62700 } }, "browserid_url": "http://127.0.0.1:62700", - "router": { "bind_to": { "port": 63300 } }, + "static": { "bind_to": { "port": 63400 } }, + "static_url": "http://127.0.0.1:63400", + "router": { "bind_to": { "port": 63300 } } // set to true to enable the development menu. "enable_development_menu": false diff --git a/lib/configuration.js b/lib/configuration.js index 1d0bd7a30883b49af16ba955e5068e698a53a367..ef4d97f42e2609563a792783337bbc04f2697663 100644 --- a/lib/configuration.js +++ b/lib/configuration.js @@ -210,6 +210,10 @@ var conf = module.exports = convict({ format: 'string?', env: 'BROWSERID_URL' }, + static_url: { + format: 'string?', + env: 'STATIC_URL' + }, process_type: 'string', email_to_console: 'boolean = false', declaration_of_support_timeout_ms: { diff --git a/lib/email.js b/lib/email.js index 5afe2fe293f1066bc774eef99f52146ae7502c3e..f87e03e404bee549e7431cc0372d3b43af8f4cd3 100644 --- a/lib/email.js +++ b/lib/email.js @@ -28,7 +28,7 @@ if (smtp_params && smtp_params.host) { } } -const TEMPLATE_PATH = path.join(__dirname, "browserid", "email_templates"); +const TEMPLATE_PATH = path.join(__dirname, "static", "email_templates"); // the underbar decorator to allow getext to extract strings function _(str) { return str; } diff --git a/lib/browserid/email_templates/add.ejs b/lib/static/email_templates/add.ejs similarity index 100% rename from lib/browserid/email_templates/add.ejs rename to lib/static/email_templates/add.ejs diff --git a/lib/browserid/email_templates/new.ejs b/lib/static/email_templates/new.ejs similarity index 100% rename from lib/browserid/email_templates/new.ejs rename to lib/static/email_templates/new.ejs diff --git a/lib/browserid/email_templates/reset.ejs b/lib/static/email_templates/reset.ejs similarity index 100% rename from lib/browserid/email_templates/reset.ejs rename to lib/static/email_templates/reset.ejs diff --git a/lib/browserid/views.js b/lib/static/views.js similarity index 99% rename from lib/browserid/views.js rename to lib/static/views.js index b26361e2c6a04ff4e381cafe12b62c80132764b1..65a5d64eb9b8d3f599f76563fc5103fd5ba96b38 100644 --- a/lib/browserid/views.js +++ b/lib/static/views.js @@ -15,6 +15,8 @@ httputils = require('../httputils.js'), etagify = require('etagify'), secrets = require('../secrets'); +require("jwcrypto/lib/algs/rs"); + // all templated content, redirects, and renames are handled here. // anything that is not an api, and not static const diff --git a/lib/wsapi.js b/lib/wsapi.js index e95a6ec4f144d13d0b981d6e3f7a79e0e87eb09b..b9c5b50f7f384ec1f2fe5336cc165b27c4441585 100644 --- a/lib/wsapi.js +++ b/lib/wsapi.js @@ -50,6 +50,8 @@ if (config.get('public_url').indexOf('https://login.persona.org') !== 0) { COOKIE_KEY += "_" + hash.digest('hex').slice(0, 6); } +const WSAPI_PREFIX = '/wsapi/'; + logger.info('session cookie name is: ' + COOKIE_KEY); function clearAuthenticatedUser(session) { @@ -75,7 +77,7 @@ function bcryptPassword(password, cb) { statsd.timing('bcrypt.encrypt_time', reqTime); cb.apply(null, arguments); }); -}; +} function authenticateSession(session, uid, level, duration_ms) { if (['assertion', 'password'].indexOf(level) === -1) @@ -116,6 +118,30 @@ function databaseDown(res, err) { httputils.serviceUnavailable(res, "database unavailable"); } +function operationFromURL (path) { + var purl = url.parse(path); + return purl.pathname.substr(0, WSAPI_PREFIX.length) === WSAPI_PREFIX && + purl.pathname.substr(WSAPI_PREFIX.length) || null; +} + +var APIs; +function allAPIs () { + if (APIs) return APIs; + + APIs = {}; + + fs.readdirSync(path.join(__dirname, 'wsapi')).forEach(function (f) { + // skip files that don't have a .js suffix or start with a dot + if (f.length <= 3 || f.substr(-3) !== '.js' || f.substr(0,1) === '.') return; + var operation = f.substr(0, f.length - 3); + + var api = require(path.join(__dirname, 'wsapi', f)); + APIs[operation] = api; + }); + + return APIs; +} + // common functions exported, for use by different api calls exports.clearAuthenticatedUser = clearAuthenticatedUser; exports.isAuthed = isAuthed; @@ -127,10 +153,6 @@ exports.langContext = langContext; exports.databaseDown = databaseDown; exports.setup = function(options, app) { - const WSAPI_PREFIX = '/wsapi/'; - - // all operations that are being forwarded - var forwardedOperations = []; // If externally we're serving content over SSL we can enable things // like strict transport security and change the way cookies are set @@ -162,8 +184,7 @@ exports.setup = function(options, app) { // by layers higher up based on cache control headers. // the fallout is that all code that interacts with sessions // should be under /wsapi - if (purl.pathname.substr(0, WSAPI_PREFIX.length) === WSAPI_PREFIX) - { + if (purl.pathname.substr(0, WSAPI_PREFIX.length) === WSAPI_PREFIX) { // explicitly disallow caching on all /wsapi calls (issue #294) resp.setHeader('Cache-Control', 'no-cache, max-age=0'); @@ -189,45 +210,31 @@ exports.setup = function(options, app) { return httputils.badRequest(resp, "no such api"); } - // if this request is to be forwarded, we will not perform request validation, - // cookie parsing, nor body parsing - leaving that up to the process we're forwarding - // to. - if (-1 !== forwardedOperations.indexOf(operation)) { - // queue up the body here on and forward a single unchunked request onto the - // writer - return bodyParser(req, resp, function() { - next(); - }); - } else if (options.router_mode) { - // skip wsapi request, let browserid middleware handle forwards - return next(); - } else { - // this is not a forwarded operation, perform full parsing and validation - return cookieParser(req, resp, function() { - bodyParser(req, resp, function() { - cookieSessionMiddleware(req, resp, function() { - // only on POSTs - if (req.method === "POST") { - - if (req.session === undefined || typeof req.session.csrf !== 'string') { // there must be a session - logger.warn("POST calls to /wsapi require a cookie to be sent, this user may have cookies disabled"); - return httputils.forbidden(resp, "no cookie"); - } - - // and the token must match what is sent in the post body - else if (!req.body || !req.session || !req.session.csrf || req.body.csrf != req.session.csrf) { - // if any of these things are false, then we'll block the request - var b = req.body ? req.body.csrf : "<none>"; - var s = req.session ? req.session.csrf : "<none>"; - logger.warn("CSRF validation failure, token mismatch. got:" + b + " want:" + s); - return httputils.badRequest(resp, "CSRF violation"); - } + // perform full parsing and validation + return cookieParser(req, resp, function() { + bodyParser(req, resp, function() { + cookieSessionMiddleware(req, resp, function() { + // only on POSTs + if (req.method === "POST") { + + if (req.session === undefined || typeof req.session.csrf !== 'string') { // there must be a session + logger.warn("POST calls to /wsapi require a cookie to be sent, this user may have cookies disabled"); + return httputils.forbidden(resp, "no cookie"); + } + + // and the token must match what is sent in the post body + else if (!req.body || !req.session || !req.session.csrf || req.body.csrf != req.session.csrf) { + // if any of these things are false, then we'll block the request + var b = req.body ? req.body.csrf : "<none>"; + var s = req.session ? req.session.csrf : "<none>"; + logger.warn("CSRF validation failure, token mismatch. got:" + b + " want:" + s); + return httputils.badRequest(resp, "CSRF violation"); } - return next(); - }); + } + return next(); }); }); - } + }); } else { return next(); } @@ -243,54 +250,25 @@ exports.setup = function(options, app) { if (op.args) { str += " - " + op.args.join(", "); } + if (op.internal) str += ' - internal'; str += ")"; logger.debug(str); } - fs.readdirSync(path.join(__dirname, 'wsapi')).forEach(function (f) { - // skip files that don't have a .js suffix or start with a dot - if (f.length <= 3 || f.substr(-3) !== '.js' || f.substr(0,1) === '.') return; - var operation = f.substr(0, f.length - 3); - + var all = allAPIs(); + Object.keys(all).forEach(function (operation) { try { - var api = require(path.join(__dirname, 'wsapi', f)); + var api = all[operation]; - // don't register read apis if we are configured as a writer, + // - don't register read apis if we are configured as a writer, // with the exception of ping which tests database connection health. - if (options.only_write_apis && !api.writes_db && - operation != 'ping') return; + // - don't register write apis if we are not configured as a writer + if ((options.only_write_apis && !api.writes_db && operation != 'ping') || + (!options.only_write_apis && api.writes_db)) + return; wsapis[operation] = api; - // forward writes if options.forward_writes is defined - if (options.forward_writes && wsapis[operation].writes_db && - !wsapis[operation].disallow_forward) - { - forwardedOperations.push(operation); - var forward_url = options.forward_writes + "/wsapi/" + operation; - wsapis[operation].process = function(req, res) { - forward(forward_url, req, res, function(err) { - if (err) { - logger.error("error forwarding '"+ operation + - "' request to '" + options.forward_writes + ":" + err); - httputils.serverError(res, "internal request forwarding error"); - } - }); - }; - - // XXX: disable validation on forwarded requests - // (we cannot perform this validation because we don't parse cookies - // nor post bodies on forwarded requests) - // - // at some point we'll want to improve our cookie parser and - // fully validate forwarded requests both at the intermediate - // hop (webhead) AND final destination (secure webhead) - - delete api.args; // deleting args will cause arg validation to be skipped - - api.authed = false; // authed=false will prevent us from checking auth status - } - // set up the argument validator if (api.args) { if (!Array.isArray(api.args)) throw "exports.args must be an array of strings"; @@ -309,30 +287,15 @@ exports.setup = function(options, app) { // debug output - all supported apis logger.debug("WSAPIs:"); Object.keys(wsapis).forEach(function(api) { - if (options.forward_writes && wsapis[api].writes_db) return; describeOperation(api, wsapis[api]); }); - if (options.forward_writes) { - logger.debug("forwarded WSAPIs (to " + options.forward_writes + "):"); - Object.keys(wsapis).forEach(function(api) { - if (wsapis[api].writes_db) { - describeOperation(api, wsapis[api]); - } - }); - } - app.use(function(req, resp, next) { var purl = url.parse(req.url); if (purl.pathname.substr(0, WSAPI_PREFIX.length) === WSAPI_PREFIX) { const operation = purl.pathname.substr(WSAPI_PREFIX.length); - if (options.router_mode && -1 === forwardedOperations.indexOf(operation)) { - // skip wsapi request, let browserid middleware handle forwards - return next(); - } - // the fake_verification wsapi is implemented elsewhere. if (operation == 'fake_verification') return next(); @@ -359,3 +322,46 @@ exports.setup = function(options, app) { } }); }; + + +exports.routeSetup = function (app, options) { + var wsapis = allAPIs(); + + app.use(function(req, resp, next) { + var operation = operationFromURL(req.url); + + // not a WSAPI request + if (!operation) return next(); + + var api = wsapis[operation]; + + // check to see if the api is known here, before spending more time with + // the request. + if (!wsapis.hasOwnProperty(operation) || + api.method.toLowerCase() !== req.method.toLowerCase()) { + // if the fake verification api is enabled (for load testing), + // then let this request fall through + if (operation !== 'fake_verification' || !process.env['BROWSERID_FAKE_VERIFICATION']) + return httputils.badRequest(resp, "no such api"); + } + + if (api.internal) { + return httputils.notFound(resp); + } + + var destination_url = api.writes_db ? options.write_url + "/wsapi/" + operation + : options.read_url + req.url; + + var cb = function() { + forward( + destination_url, req, resp, + function(err) { + if (err) { + logger.error("error forwarding request:", err); + } + }); + }; + return express.bodyParser()(req, resp, cb); + + }); +}; diff --git a/lib/wsapi/create_account_with_assertion.js b/lib/wsapi/create_account_with_assertion.js index 13f96d395fb6b56c7f4fa85adfcbc38472ea48cf..3fdd3f3608037056ab8326767946d58a91ef182e 100644 --- a/lib/wsapi/create_account_with_assertion.js +++ b/lib/wsapi/create_account_with_assertion.js @@ -12,7 +12,7 @@ logger = require('../logging.js').logger; exports.method = 'post'; exports.writes_db = true; exports.authed = false; -exports.disallow_forward = true; +exports.internal = true; exports.args = ['assertion']; exports.i18n = false; diff --git a/scripts/run_locally.js b/scripts/run_locally.js index 346fa7dda7dff3152af234e9f9fef5261e0d01fd..f034d8279d9c2e57c3d09b2154a715232d65fbf7 100755 --- a/scripts/run_locally.js +++ b/scripts/run_locally.js @@ -30,6 +30,7 @@ var daemonsToRun = { }, proxy: { }, browserid: { }, + static: { }, router: { } }; @@ -55,6 +56,7 @@ process.env['BROWSERID_URL'] = 'http://' + HOST + ":10007"; process.env['VERIFIER_URL'] = 'http://' + HOST + ":10000/verify"; process.env['KEYSIGNER_URL'] = 'http://' + HOST + ":10003"; process.env['ROUTER_URL'] = 'http://' + HOST + ":10002"; +process.env['STATIC_URL'] = 'http://' + HOST + ":10010"; process.env['PUBLIC_URL'] = process.env['ROUTER_URL']; diff --git a/tests/heartbeat-test.js b/tests/heartbeat-test.js index 815f97120003b062da83fe9d898acc0fc16ff471..6dd5341b4b586f303cc43069fdfbb5046ff8a311 100755 --- a/tests/heartbeat-test.js +++ b/tests/heartbeat-test.js @@ -114,6 +114,66 @@ suite.addBatch({ } }); +// now let's SIGSTOP the static process and verify that the router's +// deep heartbeat fails within 11s +suite.addBatch({ + "stopping the static process": { + topic: function() { + process.kill(parseInt(process.env['STATIC_PID'], 10), 'SIGSTOP'); + this.callback(); + }, + "then doing a deep __heartbeat__ on router": { + topic: function() { + var self = this; + var start = new Date(); + var req = http.get({ + host: '127.0.0.1', + port: 10002, + path: '/__heartbeat__?deep=true' + }, function(res) { + self.callback(null, res.statusCode, start); + req.abort(); + }).on('error', function(e) { + self.callback(e, null); + req.abort(); + }); + }, + "fails": function(e, code, start) { + assert.ok(!e); + assert.strictEqual(500, code); + }, + "takes about 5s": function(e, code, start) { + assert.ok(!e); + var elapsedMS = new Date() - start; + assert.ok(3000 < elapsedMS < 7000); + }, + "but upon SIGCONT": { + topic: function(e, code) { + process.kill(parseInt(process.env['STATIC_PID'], 10), 'SIGCONT'); + this.callback(); + }, + "a deep heartbeat": { + topic: function() { + var self = this; + var req = http.get( + { host: '127.0.0.1', port: 10002, path: '/__heartbeat__?deep=true'}, + function(res) { + self.callback(null, res.statusCode); + req.abort(); + }).on('error', function(e) { + self.callback(e, null); + req.abort(); + }); + }, + "works": function(err, code) { + assert.ok(!err); + assert.strictEqual(200, code); + } + } + } + } + } +}); start_stop.addShutdownBatches(suite); diff --git a/tests/internal-wsapi-test.js b/tests/internal-wsapi-test.js new file mode 100644 index 0000000000000000000000000000000000000000..59d04ae2dad6f84e5c89a72d187c12f529e7eac5 --- /dev/null +++ b/tests/internal-wsapi-test.js @@ -0,0 +1,35 @@ +#!/usr/bin/env node + +/* 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/. */ + +require('./lib/test_env.js'); + +const +assert = require('assert'), +vows = require('vows'), +start_stop = require('./lib/start-stop.js'), +wsapi = require('./lib/wsapi.js'); + +var suite = vows.describe('internal-wsapi'); + +// disable vows (often flakey?) async error behavior +suite.options.error = false; + +start_stop.addStartupBatches(suite); + +suite.addBatch({ + "requesting to create an account with an assertion": { + topic: wsapi.post('/wsapi/create_account_with_assertion', { }), + "returns a 404": function(err, r) { + assert.strictEqual(r.code, 404); + } + } +}); + +start_stop.addShutdownBatches(suite); + +// run or export the suite. +if (process.argv[1] === __filename) suite.run(); +else suite.export(module);