diff --git a/.gitignore b/.gitignore index 1823f2e5796b4df5d7c5694ff922d2e906b415a8..3641ea8f464ca6631e49512bb28279697cecc4e7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ .\#* /node_modules /var +/rpmbuild +/npm-debug.log + diff --git a/ChangeLog b/ChangeLog index f4aa9ed6447be55b03b5f0570df1925454fbcaeb..dad86b034f61d8860a027d8004537bb96f9a8f02 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,16 @@ +train-2011.10.27: + * link fixing ('need help?' to point to SUMO): #378 + * unit tests repaired: #469 (broken in fix to #82) + * improve handling of network errors: #448 + * improve styling and language of email confirmation page: #349 + * logging improvements: #455 + * RPM generation script created (for installation of browserid on redhat [moz prod] boxes): #478 + * SCHEMA CHANGES to improve database performance and scalability: #480 + * change the health check call from '/ping.txt' to '/__heartbeat__': #481 + * remove application level network timeouts (let the network stack do its job, the user can cancel if they get sick of it): #485 + * improve messaging for unsupported browsers: #273, #484 + * developer documentation improvements: #496 + train-2011.10.20: * android < 3.0 now supported: #461 * properly set assertion expiration time to when they expire, not when they're issued: #433, #457, #458 diff --git a/README.md b/README.md index 9ecb6efd18c83e9d3d9e9af4ded21a727c745e29..908b6ea4a28cb2eed5b084f9ed015dac2e7339c1 100644 --- a/README.md +++ b/README.md @@ -23,10 +23,12 @@ or changes will be made. 2. Boot up the VM: - $ cd browserid - $ vagrant up - $ vagrant ssh - vagrant@lucid32:browserid$ node ./run.js +``` +cd browserid +vagrant up +vagrant ssh vagrant@lucid32:browserid +node ./run.js +``` `vagrant up` will take a while. Go get a cup of coffee. This is because it downloads the 500MB VM. diff --git a/bin/browserid b/bin/browserid new file mode 100755 index 0000000000000000000000000000000000000000..d69fe6767558d96e9b15a8cbfa9360c435873269 --- /dev/null +++ b/bin/browserid @@ -0,0 +1,371 @@ +#!/usr/bin/env node + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla BrowserID. + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +const +fs = require('fs'), +path = require('path'), +url = require('url'), +http = require('http'); +sessions = require('connect-cookie-session'); + +// add lib/ to the require path +require.paths.unshift(path.join(__dirname, '..', 'lib')); + +const +wsapi = require('browserid/wsapi.js'), +ca = require('browserid/ca.js'), +httputils = require('httputils.js'), +express = require('express'), +secrets = require('secrets.js'), +db = require('db.js'), +config = require('configuration.js'), +heartbeat = require('heartbeat.js'), +metrics = require("metrics.js"), +logger = require("logging.js").logger, +forward = require('browserid/http_forward'), +urlparse = require('urlparse'); + +var app = undefined; + +app = express.createServer(); + +logger.info("browserid server starting up"); + +const COOKIE_SECRET = secrets.hydrateSecret('browserid_cookie', config.get('var_path')); +const COOKIE_KEY = 'browserid_state'; + +// verify that we have a keysigner configured +if (!config.get('keysigner_url')) { + logger.error('missing required configuration - url for the keysigner (KEYSIGNER_URL in env)'); + process.exit(1); +} + +function internal_redirector(new_url, suppress_noframes) { + return function(req, resp, next) { + if (suppress_noframes) + resp.removeHeader('x-frame-options'); + req.url = new_url; + return next(); + }; +} + +function router(app) { + app.set("views", path.join(__dirname, "..", "resources", "views")); + + app.set('view options', { + production: config.get('use_minified_resources') + }); + + // this should probably be an internal redirect + // as soon as relative paths are figured out. + app.get('/sign_in', function(req, res, next ) { + metrics.userEntry(req); + res.render('dialog.ejs', { + title: 'A Better Way to Sign In', + layout: 'dialog_layout.ejs', + useJavascript: true, + production: config.get('use_minified_resources') + }); + }); + + app.get("/unsupported_dialog", function(req,res) { + res.render('unsupported_dialog.ejs', {layout: 'dialog_layout.ejs', useJavascript: false}); + }); + + // simple redirects (internal for now) + app.get('/register_iframe', internal_redirector('/dialog/register_iframe.html',true)); + + // Used for a relay page for communication. + app.get("/relay", function(req,res, next) { + // Allow the relay to be run within a frame + res.removeHeader('x-frame-options'); + res.render('relay.ejs', { + layout: false, + production: config.get('use_minified_resources') + }); + }); + + + app.get('/', function(req,res) { + res.render('index.ejs', {title: 'A Better Way to Sign In', fullpage: true}); + }); + + app.get("/signup", function(req, res) { + res.render('signup.ejs', {title: 'Sign Up', fullpage: false}); + }); + + app.get("/forgot", function(req, res) { + res.render('forgot.ejs', {title: 'Forgot Password', fullpage: false, email: req.query.email}); + }); + + app.get("/signin", function(req, res) { + res.render('signin.ejs', {title: 'Sign In', fullpage: false}); + }); + + app.get("/about", function(req, res) { + res.render('about.ejs', {title: 'About', fullpage: false}); + }); + + app.get("/tos", function(req, res) { + res.render('tos.ejs', {title: 'Terms of Service', fullpage: false}); + }); + + app.get("/privacy", function(req, res) { + res.render('privacy.ejs', {title: 'Privacy Policy', fullpage: false}); + }); + + app.get("/verify_email_address", function(req, res) { + res.render('verifyuser.ejs', {title: 'Complete Registration', fullpage: true, token: req.query.token}); + }); + + app.get("/add_email_address", function(req,res) { + res.render('verifyemail.ejs', {title: 'Verify Email Address', fullpage: false}); + }); + + // REDIRECTS + REDIRECTS = { + "/manage": "/", + "/users": "/", + "/users/": "/", + "/primaries" : "/developers", + "/primaries/" : "/developers", + "/developers" : "https://github.com/mozilla/browserid/wiki/How-to-Use-BrowserID-on-Your-Site" + }; + + // 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]); + } + + // register all the WSAPI handlers + wsapi.setup(app); + + // setup health check / heartbeat + heartbeat.setup(app, function(cb) { + // let's check stuff! first the heartbeat of our keysigner + heartbeat.check(config.get('keysigner_url'), cb); + }); + + // the public key + app.get("/pk", function(req, res) { + res.json(ca.PUBLIC_KEY.toSimpleObject()); + }); + + // vep bundle of JavaScript + app.get("/vepbundle", function(req, res) { + fs.readFile(__dirname + "/../node_modules/jwcrypto/vepbundle.js", function(error, content) { + if (error) { + res.writeHead(500); + res.end("oops"); + console.log(error); + } else { + res.writeHead(200, {'Content-Type': 'text/javascript'}); + res.write(content); + res.end(); + } + }); + }); + + app.get('/code_update', function(req, resp, next) { + logger.warn("code updated. shutting down."); + process.exit(); + }); +}; + +// request to logger, dev formatted which omits personal data in the requests +app.use(express.logger({ + format: 'dev', + stream: { + write: function(x) { + logger.info(typeof x === 'string' ? x.trim() : x); + } + } +})); + +// if these are verify requests, we'll redirect them off +// to the verifier +if (config.get('verifier_url')) { + app.use(function(req, res, next) { + if (/^\/verify$/.test(req.url)) { + forward( + config.get('verifier_url'), req, res, + function(err) { + if (err) { + logger.error("error forwarding request:", err); + } + }); + } else { + return next(); + } + }); +} + +// over SSL? +var overSSL = (config.get('scheme') == 'https'); + +app.use(express.cookieParser()); + +var cookieSessionMiddleware = sessions({ + secret: COOKIE_SECRET, + key: COOKIE_KEY, + cookie: { + path: '/wsapi', + httpOnly: true, + // IMPORTANT: we allow users to go 1 weeks on the same device + // without entering their password again + maxAge: config.get('authentication_duration_ms'), + secure: overSSL + } +}); + +// cookie sessions && cache control +app.use(function(req, resp, next) { + // cookie sessions are only applied to calls to /wsapi + // as all other resources can be aggressively cached + // by layers higher up based on cache control headers. + // the fallout is that all code that interacts with sessions + // should be under /wsapi + if (/^\/wsapi/.test(req.url)) { + // explicitly disallow caching on all /wsapi calls (issue #294) + resp.setHeader('Cache-Control', 'no-cache, max-age=0'); + + // we set this parameter so the connect-cookie-session + // sends the cookie even though the local connection is HTTP + // (the load balancer does SSL) + if (overSSL) + req.connection.proxySecure = true; + + return cookieSessionMiddleware(req, resp, next); + + } else { + return next(); + } +}); + +config.performSubstitution(app); + +// verify all JSON responses are objects - prevents regression on issue #217 +app.use(function(req, resp, next) { + var realRespJSON = resp.json; + resp.json = function(obj) { + if (!obj || typeof obj !== 'object') { + logger.error("INTERNAL ERROR! *all* json responses must be objects"); + throw "internal error"; + } + realRespJSON.call(resp, obj); + }; + return next(); +}); + +app.use(express.bodyParser()); + +// Check CSRF token early. POST requests are only allowed to +// /wsapi and they always must have a valid csrf token +app.use(function(req, resp, next) { + // only on POSTs + if (req.method == "POST") { + var denied = false; + if (!/^\/wsapi/.test(req.url)) { // post requests only allowed to /wsapi + denied = true; + logger.warn("CSRF validation failure: POST only allowed to /wsapi urls. not '" + req.url + "'"); + } + + else if (req.session === undefined) { // there must be a session + denied = true; + logger.warn("CSRF validation failure: POST calls to /wsapi require an active session"); + } + + // the session must have a csrf token + else if (typeof req.session.csrf !== 'string') { + denied = true; + logger.warn("CSRF validation failure: POST calls to /wsapi require an csrf token to be set"); + } + + // and the token must match what is sent in the post body + else if (req.body.csrf != req.session.csrf) { + denied = true; + // if any of these things are false, then we'll block the request + logger.warn("CSRF validation failure, token mismatch. got:" + req.body.csrf + " want:" + req.session.csrf); + } + + if (denied) return httputils.badRequest(resp, "CSRF violation"); + + } + return next(); +}); + +// a tweak to get the content type of host-meta correct +app.use(function(req, resp, next) { + if (req.url === '/.well-known/host-meta') { + resp.setHeader('content-type', 'text/xml'); + } + next(); +}); + +// Strict Transport Security +app.use(function(req, resp, next) { + if (overSSL) { + // expires in 30 days, include subdomains like www + resp.setHeader("Strict-Transport-Security", "max-age=2592000; includeSubdomains"); + } + next(); +}); + +// prevent framing +app.use(function(req, resp, next) { + resp.setHeader('x-frame-options', 'DENY'); + next(); +}); + +// add the actual URL handlers other than static +router(app); + +// use the express 'static' middleware for serving of static files (cache headers, HTTP range, etc) +app.use(express.static(path.join(__dirname, "..", "resources", "static"))); + +// open the databse +db.open(config.get('database'), function () { + var bindTo = config.get('bind_to'); + app.listen(bindTo.port, bindTo.host, function() { + logger.info("running on http://" + app.address().address + ":" + app.address().port); + }); +}); \ No newline at end of file diff --git a/bin/keysigner b/bin/keysigner new file mode 100755 index 0000000000000000000000000000000000000000..ac5fa7f249bec8a734f218e5c2597338e239e538 --- /dev/null +++ b/bin/keysigner @@ -0,0 +1,96 @@ +#!/usr/bin/env node + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla BrowserID. + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +// I sign keys. That's what I do. + +const +path = require('path'), +express = require('express'); + +// add lib/ to the require path for our own libs +require.paths.unshift(path.join(__dirname, '..', 'lib')); + +const +config = require('configuration.js'), +validate = require('validate.js'), +metrics = require("metrics.js"), +logger = require("logging.js").logger, +ca = require('keysigner/ca.js'), +heartbeat = require('heartbeat'); + +// create an express server +var app = express.createServer(); + +// our server will log +app.use(express.logger({ + format: 'dev', + stream: { + write: function(x) { + logger.info(typeof x === 'string' ? x.trim() : x); + } + } +})); + +app.use(function(req, resp, next) { + next(); +}); + +// parse POST bodies +app.use(express.bodyParser()); + +heartbeat.setup(app); + +// and our single function +app.post('/wsapi/cert_key', validate(["email", "pubkey"]), function(req, resp) { + // parse the pubkey + var pk = ca.parsePublicKey(req.body.pubkey); + + // same account, we certify the key + // we certify it for a day for now + var expiration = new Date(); + expiration.setTime(new Date().valueOf() + config.get('certificate_validity_ms')); + var cert = ca.certify(req.body.email, pk, expiration); + + resp.writeHead(200, {'Content-Type': 'text/plain'}); + resp.write(cert); + resp.end(); +}); + +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/performance/run.js b/bin/load_gen similarity index 97% rename from performance/run.js rename to bin/load_gen index fea7840d007065bffe5d4f0721b24240b8b4a916..b3cd137a90a817b068aad1648ade916f8544f962 100755 --- a/performance/run.js +++ b/bin/load_gen @@ -20,7 +20,7 @@ * the Initial Developer. All Rights Reserved. * * Contributor(s): - * Lloyd Hilaiel <lloyd@hilaiel.com> + * Lloyd Hilaiel <lloyd@hilaiel.com> * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -40,8 +40,7 @@ * tool, which is capable of analysing the maximum active users that * a browserid deployment can support */ - -// option processing with optimist +// option processing with optimist var argv = require('optimist') .usage('Apply load to a BrowserID server.\nUsage: $0', [ "foo" ]) .alias('h', 'help') @@ -92,14 +91,14 @@ var completed = { const activitiesPerUserPerSecond = (40.0 / ( 24 * 60 * 60 )); // activities -var activity = { +var activity = { "signup": { // a %20 montly growth rate means there's a 20% probability of // the monthly activity generated by an active user being a // new user signup probability: (1.0 / (40 * 28 * .2)) }, - "reset_pass": { + "reset_pass": { // users forget their password once every 4 weeks probability: (1.0 / (40 * 28.0)) }, @@ -129,7 +128,7 @@ var activity = { // now attach "start functions" to the activity map by including // the implementation of each activity Object.keys(activity).forEach(function(k) { - activity[k].startFunc = require("./lib/" + k).startFunc; + activity[k].startFunc = require("../lib/performance/" + k).startFunc; }); // probs is a 2d array mapping normalized probabilities from 0-1 to @@ -237,12 +236,12 @@ function poll() { // how many active users would we like to simulate var targetActive = args.m; - + // if we're not throttled, then we'll trying 150% as many as // we're simulating right now. If we're not simulating at least // 10000 active users, that shall be our lower bound if (!targetActive) { - if (averages[0] > 10000) targetActive = averages[0] * 1.5; + if (averages[0] > 10000) targetActive = averages[0] * 1.5; else targetActive = 10000; } diff --git a/verifier/app.js b/bin/verifier old mode 100644 new mode 100755 similarity index 63% rename from verifier/app.js rename to bin/verifier index 8a7d95e62ca09e286dfe23b7f68b130079193f05..8da38a12f39c7d57a035635a3e1271360376af46 --- a/verifier/app.js +++ b/bin/verifier @@ -1,3 +1,5 @@ +#!/usr/bin/env node + /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * @@ -34,25 +36,49 @@ * * ***** END LICENSE BLOCK ***** */ -const path = require('path'), - url = require('url'), - fs = require('fs'), -certassertion = require('./lib/certassertion.js'), - express = require('express'), - metrics = require('../libs/metrics.js'), - logger = require('../libs/logging.js').logger; +const +sys = require("sys"), +path = require('path'), +url = require('url'), +fs = require('fs'), +express = require('express'), +certassertion = require('../lib/verifier/certassertion.js'), +metrics = require('../lib/metrics'), +heartbeat = require('../lib/heartbeat'), +logger = require('../lib/logging').logger, +config = require('../lib/configuration'); logger.info("verifier server starting up"); -// updating this call for certs now (Ben - 2011-09-06) -// assertion is the single assertion of email -// audience is the intended audience -// certificates is the list of chained certificates, CSV-style -function doVerify(req, resp, next) { +var app = express.createServer(); + +// request to logger, dev formatted which omits personal data in the requests +app.use(express.logger({ + stream: { + write: function(x) { + logger.info(typeof x === 'string' ? x.trim() : x); + } + } +})); + +app.use(express.bodyParser()); + +// code_update is an internal api that causes the node server to +// shut down. This should never be externally accessible and +// is used during the dead simple deployment procedure. +app.get("/code_update", function (req, resp) { + logger.warn("code updated. shutting down."); + process.exit(); +}); + +// setup health check / heartbeat +heartbeat.setup(app); + +app.post('/verify', function(req, resp, next) { req.body = req.body || {} - var assertion = (req.query && req.query.assertion) ? req.query.assertion : req.body.assertion; - var audience = (req.query && req.query.audience) ? req.query.audience : req.body.audience; + var assertion = req.body.assertion; + var audience = req.body.audience; if (!(assertion && audience)) return resp.json({ status: "failure", reason: "need assertion and audience" }); @@ -86,41 +112,13 @@ function doVerify(req, resp, next) { resp.json({"status":"failure", reason: (error ? error.toString() : "unknown")}); metrics.report('verify', { result: 'failure', - rp: audienceFromAssertion + rp: audience }); }); -} - -exports.setup = function(app) { - // request to logger, dev formatted which omits personal data in the requests - - app.use(express.logger({ - format: 'dev', - stream: { - write: function(x) { - logger.info(typeof x === 'string' ? x.trim() : x); - } - } - })); - - app.use(express.bodyParser()); - - // code_update is an internal api that causes the node server to - // shut down. This should never be externally accessible and - // is used during the dead simple deployment procedure. - app.get("/code_update", function (req, resp) { - logger.warn("code updated. shutting down."); - process.exit(); - }); - - // A simple ping hook for monitoring. - app.get("/ping.txt", function(req ,resp) { - resp.writeHead(200, {"Content-Type": "text/plain"}) - resp.write("k."); - resp.end(); - }); +}); - app.post('/', doVerify); - app.post('/verify', doVerify); -}; +var bindTo = config.get('bind_to'); +app.listen(bindTo.port, bindTo.host, function(conn) { + logger.info("running on http://" + app.address().address + ":" + app.address().port); +}); diff --git a/browserid/app.js b/browserid/app.js deleted file mode 100644 index a0f26efc28471de7427facc9fd6f4e6ed105a007..0000000000000000000000000000000000000000 --- a/browserid/app.js +++ /dev/null @@ -1,324 +0,0 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Mozilla BrowserID. - * - * The Initial Developer of the Original Code is Mozilla. - * Portions created by the Initial Developer are Copyright (C) 2011 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -const -fs = require('fs'), -path = require('path'), -url = require('url'), -wsapi = require('./lib/wsapi.js'), -ca = require('./lib/ca.js'), -httputils = require('./lib/httputils.js'), -sessions = require('connect-cookie-session'), -express = require('express'), -secrets = require('../libs/secrets.js'), -db = require('./lib/db.js'), -configuration = require('../libs/configuration.js'), -substitution = require('../libs/substitute.js'); -metrics = require("../libs/metrics.js"), -logger = require("../libs/logging.js").logger; - -logger.info("browserid server starting up"); - -// open the databse -db.open(configuration.get('database')); - -const COOKIE_SECRET = secrets.hydrateSecret('browserid_cookie', configuration.get('var_path')); -const COOKIE_KEY = 'browserid_state'; - -function internal_redirector(new_url, suppress_noframes) { - return function(req, resp, next) { - if (suppress_noframes) - resp.removeHeader('x-frame-options'); - req.url = new_url; - return next(); - }; -} - -function router(app) { - app.set("views", __dirname + '/views'); - - app.set('view options', { - production: configuration.get('use_minified_resources') - }); - - // this should probably be an internal redirect - // as soon as relative paths are figured out. - app.get('/sign_in', function(req, res, next ) { - metrics.userEntry(req); - res.render('dialog.ejs', { - title: 'A Better Way to Sign In', - layout: false, - production: configuration.get('use_minified_resources') - }); - }); - - // simple redirects (internal for now) - app.get('/register_iframe', internal_redirector('/dialog/register_iframe.html',true)); - - // Used for a relay page for communication. - app.get("/relay", function(req,res, next) { - // Allow the relay to be run within a frame - res.removeHeader('x-frame-options'); - res.render('relay.ejs', { - layout: false, - production: configuration.get('use_minified_resources') - }); - }); - - - app.get('/', function(req,res) { - res.render('index.ejs', {title: 'A Better Way to Sign In', fullpage: true}); - }); - - // BA removed .html URLs. If we have 404s, - // we should set up some redirects - - app.get("/signup", function(req, res) { - res.render('signup.ejs', {title: 'Sign Up', fullpage: false}); - }); - - app.get("/forgot", function(req, res) { - res.render('forgot.ejs', {title: 'Forgot Password', fullpage: false, email: req.query.email}); - }); - - app.get("/signin", function(req, res) { - res.render('signin.ejs', {title: 'Sign In', fullpage: false}); - }); - - app.get("/about", function(req, res) { - res.render('about.ejs', {title: 'About', fullpage: false}); - }); - - app.get("/tos", function(req, res) { - res.render('tos.ejs', {title: 'Terms of Service', fullpage: false}); - }); - - app.get("/privacy", function(req, res) { - res.render('privacy.ejs', {title: 'Privacy Policy', fullpage: false}); - }); - - app.get("/verify_email_address", function(req, res) { - res.render('verifyuser.ejs', {title: 'Complete Registration', fullpage: true, token: req.query.token}); - }); - - app.get("/add_email_address", function(req,res) { - res.render('verifyemail.ejs', {title: 'Verify Email Address', fullpage: false}); - }); - - // REDIRECTS - REDIRECTS = { - "/manage": "/", - "/users": "/", - "/users/": "/", - "/primaries" : "/developers", - "/primaries/" : "/developers", - "/developers" : "https://github.com/mozilla/browserid/wiki/How-to-Use-BrowserID-on-Your-Site" - }; - - // 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]); - } - - // register all the WSAPI handlers - wsapi.setup(app); - - // the public key - app.get("/pk", function(req, res) { - res.json(ca.PUBLIC_KEY.toSimpleObject()); - }); - - // vep bundle of JavaScript - app.get("/vepbundle", function(req, res) { - fs.readFile(__dirname + "/../node_modules/jwcrypto/vepbundle.js", function(error, content) { - if (error) { - res.writeHead(500); - res.end("oops"); - console.log(error); - } else { - res.writeHead(200, {'Content-Type': 'text/javascript'}); - res.write(content); - res.end(); - } - }); - }); - - app.get('/code_update', function(req, resp, next) { - logger.warn("code updated. shutting down."); - process.exit(); - }); -}; - -exports.setup = function(server) { - // request to logger, dev formatted which omits personal data in the requests - server.use(express.logger({ - format: 'dev', - stream: { - write: function(x) { - logger.info(typeof x === 'string' ? x.trim() : x); - } - } - })); - - // over SSL? - var overSSL = (configuration.get('scheme') == 'https'); - - server.use(express.cookieParser()); - - var cookieSessionMiddleware = sessions({ - secret: COOKIE_SECRET, - key: COOKIE_KEY, - cookie: { - path: '/wsapi', - httpOnly: true, - // IMPORTANT: we allow users to go 1 weeks on the same device - // without entering their password again - maxAge: configuration.get('authentication_duration_ms'), - secure: overSSL - } - }); - - // cookie sessions && cache control - server.use(function(req, resp, next) { - // cookie sessions are only applied to calls to /wsapi - // as all other resources can be aggressively cached - // by layers higher up based on cache control headers. - // the fallout is that all code that interacts with sessions - // should be under /wsapi - if (/^\/wsapi/.test(req.url)) { - // explicitly disallow caching on all /wsapi calls (issue #294) - resp.setHeader('Cache-Control', 'no-cache, max-age=0'); - - // we set this parameter so the connect-cookie-session - // sends the cookie even though the local connection is HTTP - // (the load balancer does SSL) - if (overSSL) - req.connection.proxySecure = true; - - return cookieSessionMiddleware(req, resp, next); - - } else { - return next(); - } - }); - - // verify all JSON responses are objects - prevents regression on issue #217 - server.use(function(req, resp, next) { - var realRespJSON = resp.json; - resp.json = function(obj) { - if (!obj || typeof obj !== 'object') { - logger.error("INTERNAL ERROR! *all* json responses must be objects"); - throw "internal error"; - } - realRespJSON.call(resp, obj); - }; - return next(); - }); - - server.use(express.bodyParser()); - - // Check CSRF token early. POST requests are only allowed to - // /wsapi and they always must have a valid csrf token - server.use(function(req, resp, next) { - // only on POSTs - if (req.method == "POST") { - var denied = false; - if (!/^\/wsapi/.test(req.url)) { // post requests only allowed to /wsapi - denied = true; - logger.warn("CSRF validation failure: POST only allowed to /wsapi urls. not '" + req.url + "'"); - } - - if (req.session === undefined) { // there must be a session - denied = true; - logger.warn("CSRF validation failure: POST calls to /wsapi require an active session"); - } - - // the session must have a csrf token - if (typeof req.session.csrf !== 'string') { - denied = true; - logger.warn("CSRF validation failure: POST calls to /wsapi require an csrf token to be set"); - } - - // and the token must match what is sent in the post body - if (req.body.csrf != req.session.csrf) { - denied = true; - // if any of these things are false, then we'll block the request - logger.warn("CSRF validation failure, token mismatch. got:" + req.body.csrf + " want:" + req.session.csrf); - } - - if (denied) return httputils.badRequest(resp, "CSRF violation"); - - } - return next(); - }); - - // a tweak to get the content type of host-meta correct - server.use(function(req, resp, next) { - if (req.url === '/.well-known/host-meta') { - resp.setHeader('content-type', 'text/xml'); - } - next(); - }); - - // Strict Transport Security - server.use(function(req, resp, next) { - if (overSSL) { - // expires in 30 days, include subdomains like www - resp.setHeader("Strict-Transport-Security", "max-age=2592000; includeSubdomains"); - } - next(); - }); - - // prevent framing - server.use(function(req, resp, next) { - resp.setHeader('x-frame-options', 'DENY'); - next(); - }); - - // add middleware to re-write urls if needed - configuration.performSubstitution(server); - - // add the actual URL handlers other than static - router(server); -} - -exports.shutdown = function() { - db.close(); -}; diff --git a/browserid/static/dialog/qunit.html b/browserid/static/dialog/qunit.html deleted file mode 100644 index 716ec5323d770965ab866d14c51cb36583920fce..0000000000000000000000000000000000000000 --- a/browserid/static/dialog/qunit.html +++ /dev/null @@ -1,39 +0,0 @@ -<html> - <head> - <link rel="stylesheet" type="text/css" href="/funcunit/qunit/qunit.css" /> - <title>dialog QUnit Test</title> - <script type='text/javascript' src='/vepbundle'></script> - <script type='text/javascript' src='/steal/steal.js?/dialog/test/qunit'></script> - </head> - <body> - - <h1 id="qunit-header">dialog Test Suite</h1> - <h2 id="qunit-banner"></h2> - <div id="qunit-testrunner-toolbar"></div> - <h2 id="qunit-userAgent"></h2> - <div id="test-content"> - <div id="page_controller"> - - <div id="formWrap"> - <div class="contents"></div> - </div> - - <div id="wait"> - <div class="contents"></div> - </div> - - <div id="error"> - <div class="contents"></div> - </div> - - </div> - <span id="email"></span> - <span id="cannotconfirm" class="error">Cannot confirm</span> - <span id="cannotcommunicate" class="error">Cannot communicate</span> - <span id="siteinfo" class="error"><span class="website"></span></span> - <span class=".hint">Hint</span> - </div> - <ol id="qunit-tests"></ol> - <div id="qunit-test-area"></div> - </body> -</html> diff --git a/browserid/static/dialog/resources/error-messages.js b/browserid/static/dialog/resources/error-messages.js deleted file mode 100644 index 7064d82d9f4a1fdaaccd01341763679ab4a412e0..0000000000000000000000000000000000000000 --- a/browserid/static/dialog/resources/error-messages.js +++ /dev/null @@ -1,117 +0,0 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Mozilla BrowserID. - * - * The Initial Developer of the Original Code is Mozilla. - * Portions created by the Initial Developer are Copyright (C) 2011 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ -BrowserID.Errors = (function(){ - "use strict"; - - var Errors = { - authenticate: { - type: "serverError", - title: "Error Authenticating", - message: "There was a technical problem while trying to log you in. Yucky!" - }, - - addEmail: { - type: "serverError", - title: "Error Adding Address", - message: "There was a technical problem while trying to add this email to your account. Yucky!" - }, - - checkAuthentication: { - type: "serverError", - title: "Error Checking Authentication", - message: "There was a technical problem while trying to log you in. Yucky!" - }, - - createUser: { - type: "serverError", - title: "Error Creating Account", - message: "There was a technical problem while trying to create your account. Yucky!" - }, - - getAssertion: { - type: "serverError", - title: "Error Getting Assertion", - message: "There was a technical problem while trying to authenticate you. Yucky!" - }, - - isEmailRegistered: { - type: "serverError", - title: "Error Checking Email Address", - message: "There was a technical problem while trying to check that email address. Yucky!" - }, - - logoutUser: { - type: "serverError", - title: "Logout Failed", - message: "An error was encountered while signing you out. Yucky!" - }, - - offline: { - type: "networkError", - title: "You are offline!", - message: "Unfortunately, BrowserID cannot communicate while offline!" - }, - - registration: { - type: "serverError", - title: "Registration Failed", - message: "An error was encountered and the signup cannot be completed. Yucky!" - }, - - requestPasswordReset: { - type: "serverError", - title: "Error Resetting Password", - message: "There was a technical problem while trying to reset your password." - }, - - signIn: { - type: "serverError", - title: "Signin Failed", - message: "There was an error signing in. Yucky!" - }, - - syncAddress: { - type: "serverError", - title: "Error Syncing Address", - message: "There was a technical problem while trying to synchronize your account. Yucky!" - } - - }; - - - return Errors; -}()); - - diff --git a/browserid/static/dialog/test/qunit/qunit.js b/browserid/static/dialog/test/qunit/qunit.js deleted file mode 100644 index 1733ac5548a0e89a31aa0f86527d287d7fc4d2ca..0000000000000000000000000000000000000000 --- a/browserid/static/dialog/test/qunit/qunit.js +++ /dev/null @@ -1,21 +0,0 @@ -steal("/dialog/resources/browserid.js", - "/dialog/resources/storage.js", - "/dialog/resources/tooltip.js", - "/dialog/resources/validation.js", - "/dialog/resources/underscore-min.js") - .plugins( - "jquery", - "jquery/controller", - "jquery/controller/subscribe", - "jquery/controller/view", - "jquery/view/ejs", - "funcunit/qunit") - .views('testBodyTemplate.ejs') - .views('wait.ejs') - .then("browserid_unit_test") - .then("pages/add_email_address_test") - .then("controllers/page_controller_unit_test") - .then("resources/validation_unit_test") - .then("resources/storage_unit_test") - .then("resources/network_unit_test") - .then("resources/user_unit_test") diff --git a/browserid/static/dialog/views/authenticate.ejs b/browserid/static/dialog/views/authenticate.ejs deleted file mode 100644 index 3341bac640d68f72ff23d609be20cdc9eb3e736f..0000000000000000000000000000000000000000 --- a/browserid/static/dialog/views/authenticate.ejs +++ /dev/null @@ -1,54 +0,0 @@ - <strong>Sign in using</strong> - <ul class="inputs"> - - <li> - <label for="email" class="serif">Email</label> - <input id="email" class="sans" type="email" autocapitalize="off" autocorrect="off" value="<%= email %>" /> - - <div id="email_format" class="tooltip" for="email"> - This field must be an email address. - </div> - - <div id="email_required" class="tooltip" for="email"> - The email field is required. - </div> - </li> - - <li id="hint_section" class="start"> - <p>Enter your email address to sign in to <strong><%= sitename %></strong></p> - </li> - - <li id="create_text_section" class="newuser"> - <p><strong>Welcome to BrowserID!</strong></p> - <p>This email looks new, so let's get you set up.</p> - </li> - - <li id="password_section" class="returning"> - - <label for="password" class="half serif">Password</label> - <div class="half right"> - <a id="forgotPassword" href="#">forgot your password?</a> - </div> - <input id="password" class="sans" type="password" maxlength="80"> - - - <div id="password_required" class="tooltip" for="password"> - The password field is required. - </div> - - <div id="cannot_authenticate" class="tooltip" for="password"> - The account cannot be logged in with this username and password. - </div> - </li> - - </ul> - - <div class="submit cf"> - <button class="start">next</button> - <button class="newuser">Verify Email</button> - - <button class="returning">sign in</button> - - <button class="forgot">Reset Password</button> - <button id="cancel_forgot_password" class="forgot">Cancel</button> - </div> diff --git a/browserid/static/ping.txt b/browserid/static/ping.txt deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/browserid/tests/lib/start-stop.js b/browserid/tests/lib/start-stop.js deleted file mode 100644 index f9e69b8a53d1c2021ff42fb2e01da6ee62c92311..0000000000000000000000000000000000000000 --- a/browserid/tests/lib/start-stop.js +++ /dev/null @@ -1,134 +0,0 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Mozilla BrowserID. - * - * The Initial Developer of the Original Code is Mozilla. - * Portions created by the Initial Developer are Copyright (C) 2011 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -const assert = require('assert'), - fs = require('fs'), - path = require('path'), - wsapi = require('./wsapi.js'); - -const varPath = path.join(path.dirname(path.dirname(__dirname)), "var"); - -function removeVarDir() { - try { - fs.readdirSync(varPath).forEach(function(f) { - fs.unlinkSync(path.join(varPath, f)); - }); - fs.rmdirSync(varPath); - } catch(e) {} -} - -exports.addStartupBatches = function(suite) { - suite.addBatch({ - "remove the user database": { - topic: function() { - removeVarDir(); - fs.mkdirSync(varPath, 0755); - return true; - }, - "directory should exist": function(x) { - assert.ok(fs.statSync(varPath).isDirectory()); - } - } - }); - - suite.addBatch({ - "run the server": { - topic: function() { - const server = require("../../run.js"); - server.runServer(); - return true; - }, - "server should be running": { - topic: wsapi.get('/ping.txt'), - "server is running": function (r, err) { - assert.equal(r.code, 200); - } - } - } - }); - - suite.addBatch({ - "wait for readiness": { - topic: function() { - var cb = this.callback; - require("../../lib/db.js").onReady(function() { cb(true) }); - }, - "readiness has arrived": function(v) { - assert.ok(v); - } - } - }); -}; - -exports.addShutdownBatches = function(suite) { - // stop the server - suite.addBatch({ - "stop the server": { - topic: function() { - const server = require("../../run.js"); - var cb = this.callback; - server.stopServer(function() { cb(true); }); - }, - "stopped": function(x) { - assert.strictEqual(x, true); - } - } - }); - - // stop the database - suite.addBatch({ - "stop the database": { - topic: function() { - require("../../lib/db.js").close(this.callback); - }, - "stopped": function(x) { - assert.isUndefined(x); - } - } - }); - - // clean up - suite.addBatch({ - "clean up": { - topic: function() { - removeVarDir(); - return true; - }, - "directory should not exist": function(x) { - assert.throws(function(){ fs.statSync(varPath) }); - } - } - }); -} \ No newline at end of file diff --git a/browserid/views/index.ejs b/browserid/views/index.ejs deleted file mode 100644 index 5725e35fd9d833061f1e36a5304cad662d90ef23..0000000000000000000000000000000000000000 --- a/browserid/views/index.ejs +++ /dev/null @@ -1,40 +0,0 @@ - <div id="content" style="display:none;"> - <div id="manage"> - <h1 class="serif">Account Manager</h1> - <div class="edit cf"> - <strong>Your Email Addresses</strong> - - <a id="manageAccounts" href="#">edit</a> - <a id="cancelManage" href="#">done</a> - </div> - <ul id="emailList"> - </ul> - <div id="disclaimer">You may, at any time, <a href="#" id="cancelAccount">cancel your account</a></div> - </div> - </div> - - <div id="vAlign" style="display:none;"> - <div id="signUp"> - <div id="card"><img src="/i/slit.png"></div> - <div id="hint"></div> - <div id="status"></div> - - <p>Connect with <em>BrowserID</em>, the safest & easiest way to sign in.</p> - <p> - <a class="granted info" href="/about">Take the tour</a> or - <a href="/signup" class="button granted create">sign up</a> - </p> - </div> - </div> - -<script type="text/html" id="templateUser"> - <li class="identity cf"> - <div class="email">{{ email }}</div> - <div class="activity cf"> - <button class="delete">remove</button> - <!-- removed registration info. We want to replace this with Last Used At ... --> - <!-- <abbr title="Registered: {{ created }}" class="status">Registered {{ relative }}.</abbr>--> - </div> - </li> -</script> - diff --git a/DEPLOYMENT.md b/docs/DEPLOYMENT.md similarity index 98% rename from DEPLOYMENT.md rename to docs/DEPLOYMENT.md index 07fe7cb6e67c0bed051f18f273711ae7a05ba5dd..1e8b7b606fff18ce905173a46c624df1fbc287e8 100644 --- a/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -156,16 +156,16 @@ post update hook, annotated to help you follow along: ### 5. get node servers running At this point, pushing code to gitolite will cause /home/browserid/code to be updated. Now -we need to get the servers running! Manually we can verify that the servers will run. +we need to get the servers running! Manually we can verify that the servers will run. For the browser id server: - cd /home/browserid/code/browserid && sudo -u www-data ./run.js + cd /home/browserid/code/browserid && sudo -u www-data ./run.js And for the verifier: - cd /home/browserid/code/verifier && sudo -u www-data ./run.js + cd /home/browserid/code/verifier && sudo -u www-data ./run.js -Now let's set up [monit] to restart the node.js servers: +Now let's set up [monit] to restart the node.js servers: 1. install monit: `sudo apt-get install monit` 2. enable monit by editing `/etc/default/monit` @@ -181,7 +181,7 @@ include /etc/monit.d/* <pre> #!/bin/bash -/usr/local/bin/node $1 > $(dirname $1)/error.log 2>&1 & +/usr/local/bin/node $1 > $(dirname $1)/error.log 2>&1 & </pre> 5. create a file to run the verifier at `/etc/monit.d/verifier`: @@ -192,7 +192,7 @@ check host verifier with address 127.0.0.1 as uid "www-data" and gid "www-data" stop program = "/usr/bin/pkill -f '/usr/local/bin/node /home/browserid/code/verifier/run.js'" if failed port 62800 protocol HTTP - request /ping.txt + request /__heartbeat__ with timeout 10 seconds then restart </pre> @@ -205,7 +205,7 @@ check host browserid.org with address 127.0.0.1 as uid "www-data" and gid "www-data" stop program = "/usr/bin/pkill -f '/usr/local/bin/node /home/browserid/code/browserid/run.js'" if failed port 62700 protocol HTTP - request /ping.txt + request /__heartbeat__ with timeout 10 seconds then restart </pre> diff --git a/performance/README.md b/docs/LOAD_GENERATION.md similarity index 100% rename from performance/README.md rename to docs/LOAD_GENERATION.md diff --git a/ORGANIZATION.md b/docs/ORGANIZATION.md similarity index 100% rename from ORGANIZATION.md rename to docs/ORGANIZATION.md diff --git a/rp/index.html b/example/index.html similarity index 100% rename from rp/index.html rename to example/index.html diff --git a/rp/jquery-min.js b/example/jquery-min.js similarity index 100% rename from rp/jquery-min.js rename to example/jquery-min.js diff --git a/browserid/lib/ca.js b/lib/browserid/ca.js similarity index 92% rename from browserid/lib/ca.js rename to lib/browserid/ca.js index 2c4cee8d34d0a911708026cea5f65d21eeb72fe0..ab07a592b45e1ef6b205dd49cc694d67845ad85f 100644 --- a/browserid/lib/ca.js +++ b/lib/browserid/ca.js @@ -39,8 +39,7 @@ var jwcert = require('jwcrypto/jwcert'), jwk = require('jwcrypto/jwk'), jws = require('jwcrypto/jws'), - configuration = require('../../libs/configuration'), - secrets = require('../../libs/secrets'), + configuration = require('configuration'), path = require("path"), fs = require("fs"); @@ -59,7 +58,7 @@ function parseCert(serializedCert) { function certify(email, publicKey, expiration) { if (expiration == null) throw "expiration cannot be null"; - return new jwcert.JWCert(HOSTNAME, expiration, publicKey, {email: email}).sign(secrets.SECRET_KEY); + return new jwcert.JWCert(HOSTNAME, expiration, publicKey, {email: email}).sign(configuration.get('secret_key')); } function verifyChain(certChain, cb) { @@ -70,8 +69,8 @@ function verifyChain(certChain, cb) { // for now we only do browserid.org issued keys if (issuer != HOSTNAME) return next(null); - - next(secrets.PUBLIC_KEY); + + next(exports.PUBLIC_KEY); }, cb); } @@ -80,4 +79,4 @@ exports.certify = certify; exports.verifyChain = verifyChain; exports.parsePublicKey = parsePublicKey; exports.parseCert = parseCert; -exports.PUBLIC_KEY = secrets.PUBLIC_KEY; \ No newline at end of file +exports.PUBLIC_KEY = configuration.get('public_key'); diff --git a/browserid/lib/email.js b/lib/browserid/email.js similarity index 96% rename from browserid/lib/email.js rename to lib/browserid/email.js index 7d8db0dfdc68d24aec062a39940e4b05313afe19..0a171152984cc10902f429022b1d08e79030c71b 100644 --- a/browserid/lib/email.js +++ b/lib/browserid/email.js @@ -34,13 +34,13 @@ * ***** END LICENSE BLOCK ***** */ const -db = require('./db'), +db = require('db.js'), emailer = require('nodemailer'), fs = require('fs'), path = require('path'), mustache = require('mustache'), -config = require('../../libs/configuration.js'), -logger = require('../../libs/logging.js').logger; +config = require('configuration.js'), +logger = require('logging.js').logger; /* if smtp parameters are configured, use them */ var smtp_params = config.get('smtp'); diff --git a/browserid/lib/fake_verification.js b/lib/browserid/fake_verification.js similarity index 100% rename from browserid/lib/fake_verification.js rename to lib/browserid/fake_verification.js diff --git a/lib/browserid/http_forward.js b/lib/browserid/http_forward.js new file mode 100644 index 0000000000000000000000000000000000000000..61f431f5d40fd9f7f0d4e833894278d9ab2c7621 --- /dev/null +++ b/lib/browserid/http_forward.js @@ -0,0 +1,49 @@ +const +url = require('url'), +http = require('http'), +https = require('https'), +logger = require('logging.js').logger, +querystring = require('querystring'); + +module.exports = function(dest, req, res, cb) { + var u = url.parse(dest.toString()); + + var m = u.protocol === 'http:' ? http : https; + + var preq = m.request({ + host: u.hostname, + port: u.port, + path: u.pathname, + method: req.method + }, function(pres) { + res.writeHead( + pres.statusCode, + pres.headers + ); + pres.on('data', function (chunk) { + res.write(chunk); + }).on('end', function() { + res.end(); + cb(); + }); + }).on('error', function(e) { + res.end(); + cb(e); + }); + + if (req.headers['content-type']) { + preq.setHeader('content-type', req.headers['content-type']); + } + + // if the body has already been parsed, we'll write it + if (req.body) { + var data = querystring.stringify(req.body); + preq.setHeader('content-length', data.length); + preq.write(data); + preq.end(); + } else { + req.on('data', function(chunk) { preq.write(chunk) }) + .on('end', function() { preq.end() }); + } + logger.info("forwarding request: " + req.url + " -> " + dest); +}; diff --git a/browserid/lib/prove_template.txt b/lib/browserid/prove_template.txt similarity index 100% rename from browserid/lib/prove_template.txt rename to lib/browserid/prove_template.txt diff --git a/browserid/lib/wsapi.js b/lib/browserid/wsapi.js similarity index 75% rename from browserid/lib/wsapi.js rename to lib/browserid/wsapi.js index 9c75fdbfc731bbbc1769f39ecb25efac83b9938b..e6be9ea80e1bf2c0a1aad28daa6b4518d496378c 100644 --- a/browserid/lib/wsapi.js +++ b/lib/browserid/wsapi.js @@ -39,38 +39,17 @@ // with HTTP methods and the like, apply middleware, etc. const -db = require('./db.js'), +db = require('db.js'), url = require('url'), -httputils = require('./httputils.js'), +httputils = require('httputils.js'), email = require('./email.js'), bcrypt = require('bcrypt'), crypto = require('crypto'), -logger = require('../../libs/logging.js').logger, +logger = require('logging.js').logger, ca = require('./ca.js'), -configuration = require('../../libs/configuration.js'); - -function checkParams(params) { - return function(req, resp, next) { - var params_in_request=null; - if (req.method === "POST") { - params_in_request = req.body; - } else { - params_in_request = req.query; - } - - try { - params.forEach(function(k) { - if (!params_in_request.hasOwnProperty(k) || typeof params_in_request[k] !== 'string') { - throw k; - } - }); - } catch(e) { - logger.error(e.toString()); - return httputils.badRequest(resp, "missing '" + e + "' argument"); - } - next(); - }; -} +config = require('configuration.js'), +validate = require('validate'), +forward = require('browserid/http_forward'); // log a user out, clearing everything from their session except the csrf token function clearAuthenticatedUser(session) { @@ -79,7 +58,6 @@ function clearAuthenticatedUser(session) { }); } - function setAuthenticatedUser(session, email) { session.authenticatedUser = email; session.authenticatedAt = new Date(); @@ -91,7 +69,7 @@ function isAuthed(req) { if (req.session.authenticatedUser) { if (!Date.parse(req.session.authenticatedAt) > 0) throw "bad timestamp"; if (new Date() - new Date(req.session.authenticatedAt) > - configuration.get('authentication_duration_ms')) + config.get('authentication_duration_ms')) { throw "expired"; } @@ -173,31 +151,39 @@ function setup(app) { * user via their claimed email address. Upon timeout expiry OR clickthrough * the staged user account transitions to a valid user account */ - app.post('/wsapi/stage_user', checkParams([ "email", "site" ]), function(req, resp) { + app.post('/wsapi/stage_user', validate([ "email", "site" ]), function(req, resp) { // staging a user logs you out. clearAuthenticatedUser(req.session); - try { - // upon success, stage_user returns a secret (that'll get baked into a url - // and given to the user), on failure it throws - db.stageUser(req.body.email, function(secret) { - // store the email being registered in the session data - if (!req.session) req.session = {}; + db.lastStaged(req.body.email, function (last) { + if (last && (new Date() - last) < config.get('min_time_between_emails_ms')) { + logger.warn('throttling request to stage email address ' + req.body.email + ', only ' + + ((new Date() - last) / 1000.0) + "s elapsed"); + return httputils.forbidden(resp, "throttling. try again later."); + } - // store the secret we're sending via email in the users session, as checking - // that it still exists in the database is the surest way to determine the - // status of the email verification. - req.session.pendingCreation = secret; + try { + // upon success, stage_user returns a secret (that'll get baked into a url + // and given to the user), on failure it throws + db.stageUser(req.body.email, function(secret) { + // store the email being registered in the session data + if (!req.session) req.session = {}; - resp.json({ success: true }); + // store the secret we're sending via email in the users session, as checking + // that it still exists in the database is the surest way to determine the + // status of the email verification. + req.session.pendingCreation = secret; - // let's now kick out a verification email! - email.sendNewUserEmail(req.body.email, req.body.site, secret); - }); - } catch(e) { - // we should differentiate tween' 400 and 500 here. - httputils.badRequest(resp, e.toString()); - } + resp.json({ success: true }); + + // let's now kick out a verification email! + email.sendNewUserEmail(req.body.email, req.body.site, secret); + }); + } catch(e) { + // we should differentiate tween' 400 and 500 here. + httputils.badRequest(resp, e.toString()); + } + }); }); app.get('/wsapi/user_creation_status', function(req, resp) { @@ -233,7 +219,7 @@ function setup(app) { }); function bcrypt_password(password, cb) { - var bcryptWorkFactor = configuration.get('bcrypt_work_factor'); + var bcryptWorkFactor = config.get('bcrypt_work_factor'); bcrypt.gen_salt(bcryptWorkFactor, function (err, salt) { if (err) { @@ -252,7 +238,7 @@ function setup(app) { }); }; - app.post('/wsapi/complete_user_creation', checkParams(["token", "pass"]), function(req, resp) { + app.post('/wsapi/complete_user_creation', validate(["token", "pass"]), function(req, resp) { // issue #155, valid password length is between 8 and 80 chars. if (req.body.pass.length < 8 || req.body.pass.length > 80) { httputils.badRequest(resp, "valid passwords are between 8 and 80 chars"); @@ -292,26 +278,34 @@ function setup(app) { }); }); - app.post('/wsapi/stage_email', checkAuthed, checkParams(["email", "site"]), function (req, resp) { - try { - // on failure stageEmail may throw - db.stageEmail(req.session.authenticatedUser, req.body.email, function(secret) { + app.post('/wsapi/stage_email', checkAuthed, validate(["email", "site"]), function (req, resp) { + db.lastStaged(req.body.email, function (last) { + if (last && (new Date() - last) < config.get('min_time_between_emails_ms')) { + logger.warn('throttling request to stage email address ' + req.body.email + ', only ' + + ((new Date() - last) / 1000.0) + "s elapsed"); + return httputils.forbidden(resp, "throttling. try again later."); + } - // store the email being added in session data - req.session.pendingAddition = secret; + try { + // on failure stageEmail may throw + db.stageEmail(req.session.authenticatedUser, req.body.email, function(secret) { - resp.json({ success: true }); + // store the email being added in session data + req.session.pendingAddition = secret; - // let's now kick out a verification email! - email.sendAddAddressEmail(req.body.email, req.body.site, secret); - }); - } catch(e) { - // we should differentiate tween' 400 and 500 here. - httputils.badRequest(resp, e.toString()); - } + resp.json({ success: true }); + + // let's now kick out a verification email! + email.sendAddAddressEmail(req.body.email, req.body.site, secret); + }); + } catch(e) { + // we should differentiate tween' 400 and 500 here. + httputils.badRequest(resp, e.toString()); + } + }); }); - app.get('/wsapi/email_for_token', checkParams(["token"]), function(req,resp) { + app.get('/wsapi/email_for_token', validate(["token"]), function(req,resp) { db.emailForVerificationSecret(req.query.token, function(email) { resp.json({ email: email }); }); @@ -357,7 +351,7 @@ function setup(app) { }); }); - app.post('/wsapi/complete_email_addition', checkParams(["token"]), function(req, resp) { + app.post('/wsapi/complete_email_addition', validate(["token"]), function(req, resp) { db.gotVerificationSecret(req.body.token, undefined, function(e) { if (e) { logger.warn("couldn't complete email verification: " + e); @@ -368,7 +362,7 @@ function setup(app) { }); }); - app.post('/wsapi/authenticate_user', checkParams(["email", "pass"]), function(req, resp) { + app.post('/wsapi/authenticate_user', validate(["email", "pass"]), function(req, resp) { db.checkAuth(req.body.email, function(hash) { if (typeof hash !== 'string' || typeof req.body.pass !== 'string') @@ -387,7 +381,7 @@ function setup(app) { // if the work factor has changed, update the hash here. issue #204 // NOTE: this runs asynchronously and will not delay the response - if (configuration.get('bcrypt_work_factor') != bcrypt.get_rounds(hash)) { + if (config.get('bcrypt_work_factor') != bcrypt.get_rounds(hash)) { logger.info("updating bcrypted password for email " + req.body.email); bcrypt_password(req.body.pass, function(err, hash) { db.updatePassword(req.body.email, hash, function(err) { @@ -403,7 +397,7 @@ function setup(app) { }); }); - app.post('/wsapi/remove_email', checkAuthed, checkParams(["email"]), function(req, resp) { + app.post('/wsapi/remove_email', checkAuthed, validate(["email"]), function(req, resp) { var email = req.body.email; db.removeEmail(req.session.authenticatedUser, email, function(error) { @@ -425,10 +419,10 @@ function setup(app) { }}); }); - app.post('/wsapi/cert_key', checkAuthed, checkParams(["email", "pubkey"]), function(req, resp) { + app.post('/wsapi/cert_key', checkAuthed, validate(["email", "pubkey"]), function(req, res) { db.emailsBelongToSameAccount(req.session.authenticatedUser, req.body.email, function(sameAccount) { // not same account? big fat error - if (!sameAccount) return httputils.badRequest(resp, "that email does not belong to you"); + if (!sameAccount) return httputils.badRequest(res, "that email does not belong to you"); // parse the pubkey var pk = ca.parsePublicKey(req.body.pubkey); @@ -436,13 +430,30 @@ function setup(app) { // same account, we certify the key // we certify it for a day for now var expiration = new Date(); - expiration.setTime(new Date().valueOf() + configuration.get('certificate_validity_ms')); + expiration.setTime(new Date().valueOf() + config.get('certificate_validity_ms')); var cert = ca.certify(req.body.email, pk, expiration); - resp.writeHead(200, {'Content-Type': 'text/plain'}); - resp.write(cert); - resp.end(); + res.writeHead(200, {'Content-Type': 'text/plain'}); + res.write(cert); + res.end(); + }); + +/* code to bounce the cert off of a proper keysigner (issue #460) + + db.emailsBelongToSameAccount(req.session.authenticatedUser, req.body.email, function(sameAccount) { + // not same account? big fat error + if (!sameAccount) return httputils.badRequest(res, "that email does not belong to you"); + + // forward to the keysigner! + var keysigner = config.get('keysigner_url'); + keysigner.path = '/wsapi/cert_key'; + forward(keysigner, req, res, function(err) { + if (err) { + logger.error("error forwarding request:", err); + } + }); }); +*/ }); app.post('/wsapi/logout', function(req, resp) { @@ -450,8 +461,7 @@ function setup(app) { resp.json({ success: true }); }); - // in the cert world, syncing is not necessary, - // just get a list of emails. + // returns a list of emails owned by the user // returns: // { // "foo@foo.com" : {..properties..} diff --git a/libs/configuration.js b/lib/configuration.js similarity index 57% rename from libs/configuration.js rename to lib/configuration.js index ab43bbcb58f104b752c1abffca5215b5b0407ebe..a71fc10d6312e51971130f5955f23badc99591a4 100644 --- a/libs/configuration.js +++ b/lib/configuration.js @@ -43,8 +43,11 @@ */ const -substitution = require('./substitute.js'), -path = require('path'); +postprocess = require('postprocess'), +path = require('path'), +urlparse = require('urlparse'), +secrets = require('./secrets'), +temp = require('temp'); var g_config = { }; @@ -71,60 +74,54 @@ const g_configs = { }; // production is the configuration that runs on our // public service (browserid.org) g_configs.production = { - hostname: 'browserid.org', - port: '443', - scheme: 'https', + URL: 'https://browserid.org', use_minified_resources: true, var_path: '/home/browserid/var/', database: { driver: "mysql", - user: 'browserid' + user: 'browserid', + create_schema: true }, bcrypt_work_factor: 12, authentication_duration_ms: (7 * 24 * 60 * 60 * 1000), - certificate_validity_ms: (24 * 60 * 60 * 1000) + certificate_validity_ms: (24 * 60 * 60 * 1000), + min_time_between_emails_ms: (60 * 1000) }; -// beta (diresworb.org) the only difference from production -// is the hostname -g_configs.beta = JSON.parse(JSON.stringify(g_configs.production)); -g_configs.beta.hostname = 'diresworb.org'; - -// development (dev.diresworb.org) the only difference from production -// is, again, the hostname -g_configs.development = JSON.parse(JSON.stringify(g_configs.production)); -g_configs.development.hostname = 'dev.diresworb.org'; // local development configuration g_configs.local = { - hostname: '127.0.0.1', - port: '10002', - scheme: 'http', + URL: 'http://127.0.0.1:10002', email_to_console: true, // don't send email, just dump verification URLs to console. use_minified_resources: false, var_path: path.join(__dirname, "..", "var"), database: { driver: "json" }, bcrypt_work_factor: g_configs.production.bcrypt_work_factor, authentication_duration_ms: g_configs.production.authentication_duration_ms, - certificate_validity_ms: g_configs.production.certificate_validity_ms + certificate_validity_ms: g_configs.production.certificate_validity_ms, + min_time_between_emails_ms: g_configs.production.min_time_between_emails_ms }; -Object.keys(g_configs).forEach(function(config) { - if (!g_configs[config].smtp) { - g_configs[config].smtp = { - host: process.env['SMTP_HOST'], - user: process.env['SMTP_USER'], - pass: process.env['SMTP_PASS'] - }; - } -}); +if (undefined !== process.env['NODE_EXTRA_CONFIG']) { + var fs = require('fs'); + eval(fs.readFileSync(process.env['NODE_EXTRA_CONFIG']) + ''); +} // test environments are variations on local g_configs.test_json = JSON.parse(JSON.stringify(g_configs.local)); -g_configs.test_json.database = { driver: "json", unit_test: true }; +g_configs.test_json.database = { + driver: "json", + // use a temporary path for testing + path: temp.path({suffix: '.db'}) +}; g_configs.test_mysql = JSON.parse(JSON.stringify(g_configs.local)); -g_configs.test_mysql.database = { driver: "mysql", user: "test", unit_test: true }; +g_configs.test_mysql.database = { + driver: "mysql", + user: "test", + database: "browserid_" + secrets.generate(6), + create_schema: true +}; // default deployment is local if (undefined === process.env['NODE_ENV']) { @@ -135,43 +132,83 @@ g_config = g_configs[process.env['NODE_ENV']]; if (g_config === undefined) throw "unknown environment: " + exports.get('env'); -function getPortForURL() { - if (g_config['scheme'] === 'https' && g_config['port'] === '443') return ""; - if (g_config['scheme'] === 'http' && g_config['port'] === '80') return ""; - return ":" + g_config['port']; +// what url are we running under? +{ + var ourURL = process.env['BROWSERID_URL'] || g_config['URL']; + var purl = urlparse(ourURL).validate().normalize().originOnly(); + g_config.URL = purl.toString(); + g_config.hostname = purl.host; + g_config.scheme = purl.scheme; + g_config.port = purl.port || (purl.scheme == 'https' ? 443 : 80); +} + +if (process.env['VERIFIER_URL']) { + var url = urlparse(process.env['VERIFIER_URL']).validate().normalize(); + if (!url.port) url.port = (url.scheme === 'http') ? 80 : 443; + g_config.verifier_url = url; +} + +if (process.env['KEYSIGNER_URL']) { + var url = urlparse(process.env['KEYSIGNER_URL']).validate().normalize(); + if (!url.port) url.port = (url.scheme === 'http') ? 80 : 443; + g_config.keysigner_url = url; +} + +// extract smtp params from the environment +if (!g_config.smtp) { + g_config.smtp = { + host: process.env['SMTP_HOST'], + user: process.env['SMTP_USER'], + pass: process.env['SMTP_PASS'] + }; +} + +// now handle ephemeral database configuration. Used in testing. +if (g_config.database.driver === 'mysql') { + if (process.env['MYSQL_DATABASE_NAME']) { + g_config.database.database = process.env['MYSQL_DATABASE_NAME']; + } +} else if (g_config.database.driver === 'json') { + if (process.env['JSON_DATABASE_PATH']) { + g_config.database.path = process.env['JSON_DATABASE_PATH']; + } +} + +// allow work factor to be twaddled from the environment +if (process.env['BCRYPT_WORK_FACTOR']) { + g_config.bcrypt_work_factor = parseInt(process.env['BCRYPT_WORK_FACTOR']); } -g_config['URL'] = g_config['scheme'] + '://' + g_config['hostname'] + getPortForURL(); +// what host/port shall we bind to? +g_config.bind_to = { + host: process.env['IP_ADDRESS'] || process.env['HOST'] || "127.0.0.1", + port: process.env['PORT'] || 0 +}; /* * Install middleware that will perform textual replacement on served output * to re-write urls as needed for this particular environment. * * Note, for a 'local' environment, no re-write is needed because this is - * handled at a higher level. For a 'production' env no rewrite is necc cause - * all source files are written for that environment. + * handled at a higher level. For other environments, only perform re-writing + * if the host, port, or scheme are different than https://browserid.org:443 + * (all source files always should have the production hostname written into them) */ exports.performSubstitution = function(app) { - if (process.env['NODE_ENV'] !== 'production' && - process.env['NODE_ENV'] !== 'local') { - app.use(substitution.substitute({ - 'https://browserid.org': g_config['URL'], - 'browserid.org:443': g_config['hostname'] + ':' + g_config['port'], - 'browserid.org': g_config['hostname'] + if (g_config['URL'] != 'https://browserid.org') { + app.use(postprocess.middleware(function(req, buffer) { + return buffer.toString().replace(new RegExp('https://browserid.org', 'g'), g_config['URL']); })); } }; // At the time this file is required, we'll determine the "process name" for this proc // if we can determine what type of process it is (browserid or verifier) based -// on the path, we'll use that, otherwise we'll name it 'ephemeral'. -if (process.argv[1] == path.join(__dirname, "..", "browserid", "run.js")) { - g_config['process_type'] = 'browserid'; -} else if (process.argv[1] == path.join(__dirname, "..", "verifier", "run.js")) { - g_config['process_type'] = 'verifier'; -} else { - g_config['process_type'] = 'ephemeral'; -} +// on the path, we'll use that, otherwise we'll name it 'ephemeral'. +g_config['process_type'] = path.basename(process.argv[1], ".js"); + +g_config['secret_key'] = secrets.loadSecretKey('root', exports.get('var_path')); +g_config['public_key'] = secrets.loadPublicKey('root', exports.get('var_path')); // log the process_type setTimeout(function() { diff --git a/browserid/lib/db.js b/lib/db.js similarity index 96% rename from browserid/lib/db.js rename to lib/db.js index 4545f2f71ea37ce55692d7ffee2892a45fb0a40b..e3a4092039378bd409c8f9a0f42a50d1e092c3f9 100644 --- a/browserid/lib/db.js +++ b/lib/db.js @@ -33,7 +33,7 @@ * * ***** END LICENSE BLOCK ***** */ -var logger = require('../../libs/logging.js').logger; +var logger = require('./logging.js').logger; var driver; @@ -51,7 +51,7 @@ exports.open = function(cfg, cb) { var driverName = "json"; if (cfg && cfg.driver) driverName = cfg.driver; try { - driver = require('./db_' + driverName + '.js'); + driver = require('./db/' + driverName + '.js'); } catch(e) { var msg = "FATAL: couldn't find database driver: " + driverName; console.log(msg); @@ -104,7 +104,8 @@ exports.onReady = function(f) { 'listEmails', 'removeEmail', 'cancelAccount', - 'updatePassword' + 'updatePassword', + 'lastStaged' ].forEach(function(fn) { exports[fn] = function() { checkReady(); diff --git a/browserid/lib/db_json.js b/lib/db/json.js similarity index 76% rename from browserid/lib/db_json.js rename to lib/db/json.js index 961761a9a5302344f967c672fc5d6433da560921..9b5b68095b810031fbc27e0921c49fab390af76f 100644 --- a/browserid/lib/db_json.js +++ b/lib/db/json.js @@ -41,10 +41,10 @@ const path = require('path'), fs = require('fs'), -secrets = require('../../libs/secrets'), +secrets = require('../secrets.js'), jsel = require('JSONSelect'), -logger = require('../../libs/logging.js').logger, -configuration = require('../../libs/configuration.js'), +logger = require('../logging.js').logger, +configuration = require('../configuration.js'), temp = require('temp'); // a little alias for stringify @@ -52,6 +52,8 @@ const ESC = JSON.stringify; var dbPath = path.join(configuration.get('var_path'), "authdb.json"); +var drop_on_close = undefined; + /* The JSON database. The structure is thus: * [ * { @@ -63,9 +65,11 @@ var dbPath = path.join(configuration.get('var_path'), "authdb.json"); * ] */ -var db = []; -var stagedEmails = { }; -var staged = { }; +var db = { + users: [ ], + stagedEmails: { }, + staged: { } +}; function flush() { try { @@ -75,54 +79,69 @@ function flush() { } } -// when unit_test is set in configuration, database should be -// ephemeral. which simply means we use a temp file and delete -// on close; -var delete_on_close = false; +function sync() { + try { + db = JSON.parse(fs.readFileSync(dbPath)); + } catch(e) { + logger.error("Cannot read database from " + dbPath); + } +} exports.open = function(cfg, cb) { - delete_on_close = false; - - if (cfg) { - if (cfg.unit_test) { - dbPath = temp.path({suffix: '.db'}); - delete_on_close = true; - } else if (cfg.path) { - dbPath = cfg.path; - } + if (cfg && cfg.path) { + dbPath = cfg.path; } + logger.debug("opening JSON database: " + dbPath); - try { - db = JSON.parse(fs.readFileSync(dbPath)); - } catch(e) { + if (cfg && cfg.drop_on_close) { + logger.debug("will remove database upon close"); + drop_on_close = true; } + sync(); + setTimeout(cb, 0); }; exports.close = function(cb) { flush(); - setTimeout(cb, 0); - if (delete_on_close) { - delete_on_close = false; - fs.unlink(dbPath, function(err) { }); - }; + + if (drop_on_close) { + drop_on_close = undefined; + fs.unlink(dbPath, function(err) { cb(err === null ? undefined : err); }); + } else { + setTimeout(cb, 0); + } }; exports.emailKnown = function(email, cb) { - var m = jsel.match(".emails :val(" + ESC(email) + ")", db); + sync(); + var m = jsel.match(".emails :val(" + ESC(email) + ")", db.users); setTimeout(function() { cb(m.length > 0) }, 0); }; exports.isStaged = function(email, cb) { if (cb) { setTimeout(function() { - cb(stagedEmails.hasOwnProperty(email)); + sync(); + cb(db.stagedEmails.hasOwnProperty(email)); }, 0); } }; +exports.lastStaged = function(email, cb) { + if (cb) { + sync(); + var d; + if (db.stagedEmails.hasOwnProperty(email)) { + d = new Date(db.staged[db.stagedEmails[email]].when); + } + setTimeout(function() { cb(d); }, 0); + } +}; + exports.emailsBelongToSameAccount = function(lhs, rhs, cb) { + sync(); emailToUserID(lhs, function(lhs_uid) { emailToUserID(rhs, function(rhs_uid) { cb(lhs_uid === rhs_uid); @@ -139,7 +158,7 @@ function addEmailToAccount(existing_email, email, cb) { if (userID == undefined) { cb("no such email: " + existing_email, undefined); } else { - db[userID].emails.push(email); + db.users[userID].emails.push(email); flush(); cb(); } @@ -150,45 +169,54 @@ exports.stageUser = function(email, cb) { var secret = secrets.generate(48); // overwrite previously staged users - staged[secret] = { + sync(); + db.staged[secret] = { type: "add_account", - email: email + email: email, + when: (new Date()).getTime() }; - - stagedEmails[email] = secret; + db.stagedEmails[email] = secret; + flush(); setTimeout(function() { cb(secret); }, 0); }; exports.stageEmail = function(existing_email, new_email, cb) { var secret = secrets.generate(48); + // overwrite previously staged users - staged[secret] = { + sync(); + db.staged[secret] = { type: "add_email", existing_email: existing_email, - email: new_email + email: new_email, + when: (new Date()).getTime() }; - stagedEmails[new_email] = secret; + db.stagedEmails[new_email] = secret; + flush(); + setTimeout(function() { cb(secret); }, 0); }; exports.emailForVerificationSecret = function(secret, cb) { setTimeout(function() { - cb(staged[secret]? staged[secret].email:undefined); + cb(db.staged[secret] ? db.staged[secret].email : undefined); }, 0); }; exports.gotVerificationSecret = function(secret, hash, cb) { - if (!staged.hasOwnProperty(secret)) return cb("unknown secret"); + sync(); + if (!db.staged.hasOwnProperty(secret)) return cb("unknown secret"); // simply move from staged over to the emails "database" - var o = staged[secret]; - delete staged[secret]; - delete stagedEmails[o.email]; + var o = db.staged[secret]; + delete db.staged[secret]; + delete db.stagedEmails[o.email]; + flush(); if (o.type === 'add_account') { exports.emailKnown(o.email, function(known) { function createAccount() { - db.push({ + db.users.push({ password: hash, emails: [ o.email ] }); @@ -232,25 +260,29 @@ exports.gotVerificationSecret = function(secret, hash, cb) { }; exports.checkAuth = function(email, cb) { - var m = jsel.match(":root > object:has(.emails > :val(" + ESC(email) + ")) > .password", db); + sync(); + var m = jsel.match(":root > object:has(.emails > :val(" + ESC(email) + ")) > .password", db.users); if (m.length === 0) m = undefined; else m = m[0]; setTimeout(function() { cb(m) }, 0); }; exports.updatePassword = function(email, hash, cb) { - var m = jsel.match(":root > object:has(.emails > :val(" + ESC(email) + "))", db); + sync(); + var m = jsel.match(":root > object:has(.emails > :val(" + ESC(email) + "))", db.users); var err = undefined; if (m.length === 0) err = "no such email address"; else m[0].password = hash; + flush(); setTimeout(function() { cb(err) }, 0); }; function emailToUserID(email, cb) { + sync(); var id = undefined; - for (var i = 0; i < db.length; i++) { - if (jsel.match(":val(" + JSON.stringify(email) + ")", db[i]).length) { + for (var i = 0; i < db.users.length; i++) { + if (jsel.match(":val(" + JSON.stringify(email) + ")", db.users[i]).length) { id = i; break; } @@ -261,13 +293,14 @@ function emailToUserID(email, cb) { } exports.listEmails = function(email, cb) { + sync(); // get the user id associated with this account emailToUserID(email, function(userID) { if (userID === undefined) { cb("no such email: " + email); return; } - var email_list = jsel.match(".emails string", db[userID]); + var email_list = jsel.match(".emails string", db.users[userID]); var emails = {}; for (var i=0; i < email_list.length; i++) emails[email_list[i]] = {}; @@ -277,24 +310,25 @@ exports.listEmails = function(email, cb) { }; exports.removeEmail = function(authenticated_email, email, cb) { - var m = jsel.match(".emails:has(:val("+ESC(authenticated_email)+")):has(:val("+ESC(email)+"))", db); + sync(); + var m = jsel.match(".emails:has(:val("+ESC(authenticated_email)+")):has(:val("+ESC(email)+"))", db.users); if (m.length) { var emails = m[0]; for (var i = 0; i < emails.length; i++) { if (emails[i] === email) { emails.splice(i, 1); + flush(); break; } } } - setTimeout(function() { cb(); }, 0); }; exports.cancelAccount = function(authenticated_email, cb) { emailToUserID(authenticated_email, function(user_id) { - db.splice(user_id, 1); + db.users.splice(user_id, 1); flush(); cb(); }); diff --git a/browserid/lib/db_mysql.js b/lib/db/mysql.js similarity index 84% rename from browserid/lib/db_mysql.js rename to lib/db/mysql.js index 06bd219bf904cd91e58c2398abb282bc108de24c..3657ff36e8f97bb9032d3516324f313b69523565 100644 --- a/browserid/lib/db_mysql.js +++ b/lib/db/mysql.js @@ -49,6 +49,7 @@ * * * +------ staged ----------+ + * |*int id | * |*string secret | * | bool new_acct | * | string existing | @@ -59,18 +60,36 @@ const mysql = require('mysql'), -secrets = require('../../libs/secrets'), -logger = require('../../libs/logging.js').logger; +secrets = require('../secrets.js'), +logger = require('../logging.js').logger; var client = undefined; // may get defined at open() time causing a database to be dropped upon connection closing. var drop_on_close = undefined; +// If you change these schemas, please notify <services-ops@mozilla.com> const schemas = [ - "CREATE TABLE IF NOT EXISTS user ( id INTEGER AUTO_INCREMENT PRIMARY KEY, passwd VARCHAR(64) );", - "CREATE TABLE IF NOT EXISTS email ( id INTEGER AUTO_INCREMENT PRIMARY KEY, user INTEGER, INDEX(user), address VARCHAR(255) UNIQUE, INDEX(address) );", - "CREATE TABLE IF NOT EXISTS staged ( secret VARCHAR(48) PRIMARY KEY, new_acct BOOL, existing VARCHAR(255), email VARCHAR(255) UNIQUE, INDEX(email), ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP);" + "CREATE TABLE IF NOT EXISTS user (" + + "id BIGINT AUTO_INCREMENT PRIMARY KEY," + + "passwd CHAR(64) NOT NULL" + + ") ENGINE=InnoDB;", + + "CREATE TABLE IF NOT EXISTS email (" + + "id BIGINT AUTO_INCREMENT PRIMARY KEY," + + "user BIGINT NOT NULL," + + "address VARCHAR(255) UNIQUE NOT NULL," + + "FOREIGN KEY user_fkey (user) REFERENCES user(id)" + + ") ENGINE=InnoDB;", + + "CREATE TABLE IF NOT EXISTS staged (" + + "id BIGINT AUTO_INCREMENT PRIMARY KEY," + + "secret CHAR(48) UNIQUE NOT NULL," + + "new_acct BOOL NOT NULL," + + "existing VARCHAR(255)," + + "email VARCHAR(255) UNIQUE NOT NULL," + + "ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL" + + ") ENGINE=InnoDB;", ]; // log an unexpected database error @@ -105,43 +124,45 @@ exports.open = function(cfg, cb) { // let's figure out the database name var database = cfg.database; if (!database) database = "browserid"; - if (cfg.unit_test) { - database += "_" + secrets.generate(8); - drop_on_close = database; - } + + // if the client specifies a name other than 'browserid', and specifies + // that we should drop the database on close, do it + if (database !== 'browserid' && cfg.drop_on_close) drop_on_close = database; // now create the databse - client.query("CREATE DATABASE IF NOT EXISTS " + database, function(err) { - if (err) { - logUnexpectedError(err); - cb(err); - return; - } - client.useDatabase(database, function(err) { + if (cfg.create_schema) { + client.query("CREATE DATABASE IF NOT EXISTS " + database, function(err) { if (err) { logUnexpectedError(err); cb(err); return; } + client.useDatabase(database, function(err) { + if (err) { + logUnexpectedError(err); + cb(err); + return; + } - // now create tables - function createNextTable(i) { - if (i < schemas.length) { - client.query(schemas[i], function(err) { - if (err) { - logUnexpectedError(err); - cb(err); - } else { - createNextTable(i+1); - } - }); - } else { - cb(); + // now create tables + function createNextTable(i) { + if (i < schemas.length) { + client.query(schemas[i], function(err) { + if (err) { + logUnexpectedError(err); + cb(err); + } else { + createNextTable(i+1); + } + }); + } else { + cb(); + } } - } - createNextTable(0); + createNextTable(0); + }); }); - }); + }; }; exports.close = function(cb) { @@ -183,11 +204,22 @@ exports.isStaged = function(email, cb) { ); } +exports.lastStaged = function(email, cb) { + client.query( + "SELECT UNIX_TIMESTAMP(ts) as ts FROM staged WHERE email = ?", [ email ], + function(err, rows) { + if (err) logUnexpectedError(err); + if (!rows || rows.length === 0) cb(); + else cb(new Date(rows[0].ts * 1000)); + } + ); +} + exports.stageUser = function(email, cb) { var secret = secrets.generate(48); // overwrite previously staged users client.query('INSERT INTO staged (secret, new_acct, email) VALUES(?,TRUE,?) ' + - 'ON DUPLICATE KEY UPDATE secret=?, existing="", new_acct=TRUE', + 'ON DUPLICATE KEY UPDATE secret=?, existing="", new_acct=TRUE, ts=NOW()', [ secret, email, secret], function(err) { if (err) { @@ -285,7 +317,7 @@ exports.stageEmail = function(existing_email, new_email, cb) { var secret = secrets.generate(48); // overwrite previously staged users client.query('INSERT INTO staged (secret, new_acct, existing, email) VALUES(?,FALSE,?,?) ' + - 'ON DUPLICATE KEY UPDATE secret=?, existing=?, new_acct=FALSE', + 'ON DUPLICATE KEY UPDATE secret=?, existing=?, new_acct=FALSE, ts=NOW()', [ secret, existing_email, new_email, secret, existing_email], function(err) { if (err) { diff --git a/lib/heartbeat.js b/lib/heartbeat.js new file mode 100644 index 0000000000000000000000000000000000000000..faa697c013c543b9a61338f76a589feb2d9b4902 --- /dev/null +++ b/lib/heartbeat.js @@ -0,0 +1,38 @@ +const urlparse = require('urlparse'); + +// the path that heartbeats live at +exports.path = '/__heartbeat__'; + +// a helper function to set up a heartbeat check +exports.setup = function(app, cb) { + app.get(exports.path, function(req, res) { + function ok(yeah) { + res.writeHead(yeah ? 200 : 500); + res.write(yeah ? 'ok' : 'not ok'); + res.end(); + } + if (cb) cb(ok); + else ok(true); + }); +}; + +// a function to check the heartbeat of a remote server +exports.check = function(url, cb) { + if (typeof url === 'string') url = urlparse(url).normalize().validate(); + else if (typeof url !== 'object') throw "url string or object required as argumnet to heartbeat.check"; + if (!url.port) url.port = (url.scheme === 'http') ? 80 : 443; + + var shortname = url.host + ':' + url.port; + + require(url.scheme).get({ + host: url.host, + port: url.port, + path: exports.path + }, function (res) { + if (res.statusCode === 200) cb(true); + else logger.error("non-200 response from " + shortname + ". fatal! (" + res.statusCode + ")"); + }, function (e) { + logger.error("can't communicate with " + shortname + ". fatal: " + e); + cb(false); + }); +}; \ No newline at end of file diff --git a/browserid/lib/httputils.js b/lib/httputils.js similarity index 93% rename from browserid/lib/httputils.js rename to lib/httputils.js index f88539a9b06dfc40c448fdef9e5cac1c5032bc2e..6b389251d2d807da6b86106a53b0e33b45df4810 100644 --- a/browserid/lib/httputils.js +++ b/lib/httputils.js @@ -63,6 +63,16 @@ exports.badRequest = function(resp, reason) resp.end(); }; +exports.forbidden = function(resp, reason) +{ + resp.writeHead(403, {"Content-Type": "text/plain"}); + resp.write("Forbidden"); + if (reason) { + resp.write(": " + reason); + } + resp.end(); +}; + exports.jsonResponse = function(resp, obj) { resp.writeHead(200, {"Content-Type": "application/json"}); diff --git a/lib/keysigner/ca.js b/lib/keysigner/ca.js new file mode 100644 index 0000000000000000000000000000000000000000..ab07a592b45e1ef6b205dd49cc694d67845ad85f --- /dev/null +++ b/lib/keysigner/ca.js @@ -0,0 +1,82 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla BrowserID. + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Ben Adida <benadida@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +// certificate authority + +var jwcert = require('jwcrypto/jwcert'), + jwk = require('jwcrypto/jwk'), + jws = require('jwcrypto/jws'), + configuration = require('configuration'), + path = require("path"), + fs = require("fs"); + +var HOSTNAME = configuration.get('hostname'); + +function parsePublicKey(serializedPK) { + return jwk.PublicKey.deserialize(serializedPK); +} + +function parseCert(serializedCert) { + var cert = new jwcert.JWCert(); + cert.parse(serializedCert); + return cert; +} + +function certify(email, publicKey, expiration) { + if (expiration == null) + throw "expiration cannot be null"; + return new jwcert.JWCert(HOSTNAME, expiration, publicKey, {email: email}).sign(configuration.get('secret_key')); +} + +function verifyChain(certChain, cb) { + // raw certs + return jwcert.JWCert.verifyChain( + certChain, new Date(), + function(issuer, next) { + // for now we only do browserid.org issued keys + if (issuer != HOSTNAME) + return next(null); + + next(exports.PUBLIC_KEY); + }, cb); +} + +// exports, not the key stuff +exports.certify = certify; +exports.verifyChain = verifyChain; +exports.parsePublicKey = parsePublicKey; +exports.parseCert = parseCert; +exports.PUBLIC_KEY = configuration.get('public_key'); diff --git a/performance/lib/add_email.js b/lib/load_gen/add_email.js similarity index 100% rename from performance/lib/add_email.js rename to lib/load_gen/add_email.js diff --git a/performance/lib/include_only.js b/lib/load_gen/include_only.js similarity index 100% rename from performance/lib/include_only.js rename to lib/load_gen/include_only.js diff --git a/performance/lib/reauth.js b/lib/load_gen/reauth.js similarity index 100% rename from performance/lib/reauth.js rename to lib/load_gen/reauth.js diff --git a/performance/lib/reset_pass.js b/lib/load_gen/reset_pass.js similarity index 100% rename from performance/lib/reset_pass.js rename to lib/load_gen/reset_pass.js diff --git a/performance/lib/signin.js b/lib/load_gen/signin.js similarity index 100% rename from performance/lib/signin.js rename to lib/load_gen/signin.js diff --git a/performance/lib/signup.js b/lib/load_gen/signup.js similarity index 100% rename from performance/lib/signup.js rename to lib/load_gen/signup.js diff --git a/performance/lib/test.js b/lib/load_gen/test.js similarity index 100% rename from performance/lib/test.js rename to lib/load_gen/test.js diff --git a/performance/lib/user_db.js b/lib/load_gen/user_db.js similarity index 100% rename from performance/lib/user_db.js rename to lib/load_gen/user_db.js diff --git a/libs/logging.js b/lib/logging.js similarity index 97% rename from libs/logging.js rename to lib/logging.js index b167c6eb0abdabc0c4be66bd0838849f85161720..3e7feb5a1a7d7e972f572b5c4923aaf205c4471f 100644 --- a/libs/logging.js +++ b/lib/logging.js @@ -78,4 +78,6 @@ exports.logger.emitErrs = false; exports.enableConsoleLogging = function() { exports.logger.add(winston.transports.Console, { colorize: true }); -}; \ No newline at end of file +}; + +if (process.env['LOG_TO_CONSOLE']) exports.enableConsoleLogging(); diff --git a/libs/metrics.js b/lib/metrics.js similarity index 100% rename from libs/metrics.js rename to lib/metrics.js diff --git a/libs/secrets.js b/lib/secrets.js similarity index 93% rename from libs/secrets.js rename to lib/secrets.js index 46ea829f270a236580a16743e92b8217c8bb3ea3..6382d5e4220b2883973d61afd6f68a849c7c8a84 100644 --- a/libs/secrets.js +++ b/lib/secrets.js @@ -57,12 +57,14 @@ exports.hydrateSecret = function(name, dir) { if (secret === undefined) { secret = exports.generate(128); + fs.writeFileSync(p, ''); + fs.chmodSync(p, 0600); fs.writeFileSync(p, secret); } return secret; }; -function loadSecretKey(name, dir) { +exports.loadSecretKey = function(name, dir) { var p = path.join(dir, name + ".secretkey"); var fileExists = false; var secret = undefined; @@ -77,7 +79,7 @@ function loadSecretKey(name, dir) { return jwk.SecretKey.deserialize(secret); } -function loadPublicKey(name, dir) { +exports.loadPublicKey = function(name, dir) { var p = path.join(dir, name + ".publickey"); var fileExists = false; var secret = undefined; @@ -93,6 +95,3 @@ function loadPublicKey(name, dir) { // {alg: <ALG>, value: <SERIALIZED_KEY>} return jwk.PublicKey.deserialize(secret); } - -exports.SECRET_KEY = loadSecretKey('root', configuration.get('var_path')); -exports.PUBLIC_KEY = loadPublicKey('root', configuration.get('var_path')); diff --git a/browserid/static/js/pages/forgot.js b/lib/validate.js similarity index 64% rename from browserid/static/js/pages/forgot.js rename to lib/validate.js index 1ab1bb6f0d1b4a9732829c8b8f1ac8df2d8dbe01..56161cea85c97b3a0f805bec2b8577eed8885349 100644 --- a/browserid/static/js/pages/forgot.js +++ b/lib/validate.js @@ -1,4 +1,3 @@ -/*globals BrowserID: true, $:true */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * @@ -19,6 +18,7 @@ * the Initial Developer. All Rights Reserved. * * Contributor(s): + * Lloyd Hilaiel <lloyd@hilaiel.com> * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -34,36 +34,39 @@ * * ***** END LICENSE BLOCK ***** */ -BrowserID.forgot = (function() { - "use strict"; +// a teensy tinsy module to do parameter validation. A good candiate for future +// librification. +// +// usage: +// +// const validate = require('validate.js'); +// +// app.post('/wsapi/foo', validate([ "email", "site" ]), function(req, resp) { +// }); - return function() { - $("form input[autofocus]").focus(); +const +logger = require('./logging.js').logger, +httputils = require('httputils.js'); - $("#signUpForm").bind("submit", function(event) { - event.preventDefault(); - $(".notifications .notification").hide(); +module.exports = function (params) { + return function(req, resp, next) { + var params_in_request=null; + if (req.method === "POST") { + params_in_request = req.body; + } else { + params_in_request = req.query; + } - var email = $("#email").val(), - password = $("#password").val(), - vpassword = $("#vpassword").val(); - - if (password != vpassword) { - $(".notifications .notification.mismatchpassword").fadeIn(); - return false; - } - - BrowserID.User.createUser(email, function onSuccess(keypair) { - $('#sent_to_email').html(email); - $('#forminputs').fadeOut(); - $(".notifications .notification.emailsent").fadeIn(); - }, function onFailure() { - // bad authentication - $(".notifications .notification.doh").fadeIn(); + try { + params.forEach(function(k) { + if (!params_in_request || !params_in_request.hasOwnProperty(k) || typeof params_in_request[k] !== 'string') { + throw k; + } }); - }); + next(); + } catch(e) { + logger.error(e.toString()); + return httputils.badRequest(resp, "missing '" + e + "' argument"); + } }; - - -}()); - +}; diff --git a/verifier/lib/certassertion.js b/lib/verifier/certassertion.js similarity index 93% rename from verifier/lib/certassertion.js rename to lib/verifier/certassertion.js index e372a0b7515c762c058c0b51a8dfaf2e480b7d5a..618867087547fb0c89ebf9bb498a9292815116d6 100644 --- a/verifier/lib/certassertion.js +++ b/lib/verifier/certassertion.js @@ -45,21 +45,17 @@ jwk = require("jwcrypto/jwk"), jwt = require("jwcrypto/jwt"), jwcert = require("jwcrypto/jwcert"), vep = require("jwcrypto/vep"), -configuration = require('../../libs/configuration'), -secrets = require('../../libs/secrets'), -logger = require("../../libs/logging.js").logger; - -// configuration information to check the issuer -const config = require("../../libs/configuration.js"); +config = require("../../lib/configuration.js"), +logger = require("../../lib/logging.js").logger; const HOSTMETA_URL = "/.well-known/host-meta"; var publicKeys = {}; // set up some default public keys -publicKeys[configuration.get('hostname')] = secrets.PUBLIC_KEY; +publicKeys[config.get('hostname')] = config.get('public_key'); logger.debug("pre-seeded public key cache with key for " + - configuration.get('hostname')); + config.get('hostname')); function https_complete_get(host, url, successCB, errorCB) { https.get({host: host,path: url}, function(res) { @@ -71,7 +67,7 @@ function https_complete_get(host, url, successCB, errorCB) { res.on('end', function() { successCB(allData); }); - + }).on('error', function(e) { console.log(e.toString()); errorCB(e); @@ -142,6 +138,8 @@ function retrieveHostPublicKey(host, successCB, errorCB) { // it might be strangely formed. function compareAudiences(want, got) { try { + var checkHostOnly = false; + // issue #82 - for a limited time, let's allow got to be sloppy and omit scheme // in which case we guess a scheme based on port if (!/^https?:\/\//.test(got)) { @@ -149,6 +147,7 @@ function compareAudiences(want, got) { var scheme = "http"; if (x.length === 2 && x[1] === '443') scheme = "https"; got = scheme + "://" + got; + checkHostOnly = true; } // now parse and compare @@ -161,9 +160,11 @@ function compareAudiences(want, got) { got = normalizeParsedURL(url.parse(got)); + if (checkHostOnly) return want.hostname === got.hostname; + return (want.protocol === got.protocol && want.hostname === got.hostname && - want.port === got.port); + want.port == got.port); } catch(e) { return false; } @@ -192,7 +193,7 @@ function verify(assertion, audience, successCB, errorCB, pkRetriever) { retrieveHostPublicKey(issuer, next, function(err) {next(null);}); }, function(pk, principal) { // primary? - if (theIssuer != configuration.get('hostname')) { + if (theIssuer != config.get('hostname')) { // then the email better match the issuer console.log(principal); if (!principal.email.match("@" + theIssuer + "$")) diff --git a/libs/wsapi_client.js b/lib/wsapi_client.js similarity index 99% rename from libs/wsapi_client.js rename to lib/wsapi_client.js index 292a9772c08933b6af4a4d341bdeac27b7257ef7..29eeabec6db9d852dcba220ead0e3990a06f8833 100644 --- a/libs/wsapi_client.js +++ b/lib/wsapi_client.js @@ -54,7 +54,7 @@ function injectCookies(ctx, headers) { headers['Cookie'] += k + "=" + ctx.cookieJar[k]; } } -} +} function extractCookies(ctx, res) { if (ctx.cookieJar === undefined) ctx.cookieJar = {}; @@ -87,7 +87,7 @@ exports.get = function(cfg, path, context, getArgs, cb) { cb(false); return; } - + var headers = { }; injectCookies(context, headers); diff --git a/libs/substitute.js b/libs/substitute.js deleted file mode 100644 index 2c4c526aedbbaac25231c855ebddd27fcd26287b..0000000000000000000000000000000000000000 --- a/libs/substitute.js +++ /dev/null @@ -1,106 +0,0 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Mozilla BrowserID. - * - * The Initial Developer of the Original Code is Mozilla. - * Portions created by the Initial Developer are Copyright (C) 2011 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -// return a function that is substitution middleware, capable -// of being installed to perform textual replacement on -// all server output -exports.substitute = function(subs) { - // given a buffer, find and replace all subs - function subHostNames(data) { - for (var i in subs) { - data = data.toString().replace(new RegExp(i, 'g'), subs[i]); - } - - return data; - } - - return function(req, resp, next) { - // cache the *real* functions - var realWrite = resp.write; - var realEnd = resp.end; - var realWriteHead = resp.writeHead; - var realSend = resp.send; - - var buf = undefined; - var enc = undefined; - var contentType = undefined; - - resp.writeHead = function (sc, reason, hdrs) { - var h = undefined; - if (typeof hdrs === 'object') h = hdrs; - else if (typeof reason === 'object') h = reason; - for (var k in h) { - if (k.toLowerCase() === 'content-type') { - contentType = h[k]; - break; - } - } - if (!contentType) contentType = resp.getHeader('content-type'); - if (!contentType) contentType = "application/unknown"; - realWriteHead.call(resp, sc, reason, hdrs); - }; - - resp.write = function (chunk, encoding) { - if (buf) buf += chunk; - else buf = chunk; - enc = encoding; - }; - - resp.send = function(stuff) { - buf = stuff; - realSend.call(resp,stuff); - }; - - resp.end = function() { - if (!contentType) contentType = resp.getHeader('content-type'); - if (contentType && (contentType === "application/javascript" || - contentType.substr(0,4) === 'text')) - { - if (buf) { - if (Buffer.isBuffer(buf)) buf = buf.toString('utf8'); - var l = Buffer.byteLength(buf); - buf = subHostNames(buf); - if (l != Buffer.byteLength(buf)) resp.setHeader('Content-Length', Buffer.byteLength(buf)); - } - } - if (buf && buf.length) { - realWrite.call(resp, buf, enc); - } - realEnd.call(resp); - } - - next(); - }; -}; diff --git a/package.json b/package.json index 4c650ea6142b5d9a6464a3a5e713b9506d49a89a..b56ddad2a88986421673c40b17bb08fd0fbcda8c 100644 --- a/package.json +++ b/package.json @@ -26,10 +26,12 @@ , "sax" : "0.2.3" , "mimelib-noiconv" : "0.1.3" , "jwcrypto": "https://github.com/mozilla/jwcrypto/tarball/3a21befabfff" + , "postprocess": "0.0.3" + , "urlparse": "0.0.1" } , "scripts": { "postinstall": "./scripts/generate_ephemeral_keys.sh", "test": "./scripts/run_all_tests.sh", - "start": "node ./run.js" + "start": "./scripts/run_locally.js" } } diff --git a/browserid/.gitignore b/resources/.gitignore similarity index 50% rename from browserid/.gitignore rename to resources/.gitignore index 87ff02149168e118b7f8f73ee8850cab949e80f6..ad546db10e4922983382c9235cb61b007e8d2475 100644 --- a/browserid/.gitignore +++ b/resources/.gitignore @@ -1,7 +1,9 @@ -/static/dialog/production.js -/static/dialog/production.css /static/css/browserid.css /static/css/browserid.min.css -/static/js/lib.min.js +/static/dialog/css/production.css +/static/dialog/css/production.min.css +/static/dialog/production.js +/static/include.orig.js /static/js/lib.js -/var +/static/js/lib.min.js +/static/relay/production.js diff --git a/browserid/assets/account-buttons.png b/resources/assets/account-buttons.png similarity index 100% rename from browserid/assets/account-buttons.png rename to resources/assets/account-buttons.png diff --git a/browserid/assets/browserID-135x35.png b/resources/assets/browserID-135x35.png similarity index 100% rename from browserid/assets/browserID-135x35.png rename to resources/assets/browserID-135x35.png diff --git a/browserid/assets/browserID-366x72.png b/resources/assets/browserID-366x72.png similarity index 100% rename from browserid/assets/browserID-366x72.png rename to resources/assets/browserID-366x72.png diff --git a/browserid/assets/browserID-80x20.png b/resources/assets/browserID-80x20.png similarity index 100% rename from browserid/assets/browserID-80x20.png rename to resources/assets/browserID-80x20.png diff --git a/browserid/assets/browserID-buttons.psd b/resources/assets/browserID-buttons.psd similarity index 100% rename from browserid/assets/browserID-buttons.psd rename to resources/assets/browserID-buttons.psd diff --git a/browserid/assets/browserID-logo.eps b/resources/assets/browserID-logo.eps similarity index 100% rename from browserid/assets/browserID-logo.eps rename to resources/assets/browserID-logo.eps diff --git a/browserid/static/.well-known/host-meta b/resources/static/.well-known/host-meta similarity index 100% rename from browserid/static/.well-known/host-meta rename to resources/static/.well-known/host-meta diff --git a/browserid/static/css/m.css b/resources/static/css/m.css similarity index 100% rename from browserid/static/css/m.css rename to resources/static/css/m.css diff --git a/browserid/static/css/sil.ttf b/resources/static/css/sil.ttf similarity index 100% rename from browserid/static/css/sil.ttf rename to resources/static/css/sil.ttf diff --git a/browserid/static/css/style.css b/resources/static/css/style.css similarity index 98% rename from browserid/static/css/style.css rename to resources/static/css/style.css index 7114748d0e4083e36fd1853a151ce6efa082dca2..64e8c5f177abf5758169124608e60b5de50d8b5f 100644 --- a/browserid/static/css/style.css +++ b/resources/static/css/style.css @@ -21,6 +21,18 @@ body { overflow-y: scroll; } +noscript { + position: fixed; + display: block; + background-color: #ef1010; + top: 0; + left: 0; + padding: 1px; + width: 100%; + color: #fff; + text-align: center; +} + /* for floats */ .cf:after { content: "."; @@ -106,7 +118,7 @@ hr { #content { padding: 84px 0; - display: none; + /* display: none;*/ } #about { @@ -724,7 +736,7 @@ h1 { } -#signUpForm #siteinfo, #congrats { +#congrats #siteinfo, #congrats { display: none; } diff --git a/browserid/static/css/ts.ttf b/resources/static/css/ts.ttf similarity index 100% rename from browserid/static/css/ts.ttf rename to resources/static/css/ts.ttf diff --git a/browserid/static/dialog/controllers/authenticate_controller.js b/resources/static/dialog/controllers/authenticate_controller.js similarity index 90% rename from browserid/static/dialog/controllers/authenticate_controller.js rename to resources/static/dialog/controllers/authenticate_controller.js index 84632f5f3008fe152f92d6c27a29a3b22899e047..21354b2b3ed27b4c5abe357b22da1eb0e1bff1e6 100644 --- a/browserid/static/dialog/controllers/authenticate_controller.js +++ b/resources/static/dialog/controllers/authenticate_controller.js @@ -42,6 +42,7 @@ user = bid.User, errors = bid.Errors, validation = bid.Validation, + tooltip = bid.Tooltip, lastEmail = ""; function checkEmail(el, event) { @@ -50,9 +51,7 @@ cancelEvent(event); - if (!validation.email(email)) { - return; - } + if (!validation.email(email)) return; user.isEmailRegistered(email, function onComplete(registered) { if (registered) { @@ -70,19 +69,16 @@ cancelEvent(event); - if (!validation.email(email)) { - return; - } + if (!validation.email(email)) return; - user.createUser(email, function(keypair) { - if (keypair) { + user.createUser(email, function(staged) { + if (staged) { self.close("user_staged", { - email: email, - keypair: keypair + email: email }); } else { - // XXX can't register this email address. + tooltip.showTooltip("#could_not_add"); } }, self.getErrorDialog(errors.createUser)); } @@ -94,9 +90,7 @@ cancelEvent(event); - if (!validation.emailAndPassword(email, pass)) { - return; - } + if (!validation.emailAndPassword(email, pass)) return; user.authenticate(email, pass, function onComplete(authenticated) { @@ -131,16 +125,12 @@ } function cancelEvent(event) { - if (event) { - event.preventDefault(); - } + if (event) event.preventDefault(); } function enterEmailState(el, event) { - if (event && event.which === 13) { - // Enter key, do nothing - return; - } + // Enter key, do nothing + if (event && event.which === 13) return; if (!el.is(":disabled")) { this.submit = checkEmail; @@ -185,6 +175,10 @@ init: function(el, options) { options = options || {}; + if (options.user) { + user = options.user; + } + this._super(el, { bodyTemplate: "authenticate.ejs", bodyVars: { @@ -196,9 +190,7 @@ this.submit = checkEmail; // If we already have an email address, check if it is valid, if so, show // password. - if (options.email) { - this.submit(); - } + if (options.email) this.submit(); }, "#email keyup": function(el, event) { diff --git a/browserid/static/dialog/controllers/checkregistration_controller.js b/resources/static/dialog/controllers/checkregistration_controller.js similarity index 100% rename from browserid/static/dialog/controllers/checkregistration_controller.js rename to resources/static/dialog/controllers/checkregistration_controller.js diff --git a/browserid/static/dialog/controllers/dialog_controller.js b/resources/static/dialog/controllers/dialog_controller.js similarity index 86% rename from browserid/static/dialog/controllers/dialog_controller.js rename to resources/static/dialog/controllers/dialog_controller.js index f25b2612410bb50b136106b6be49d5a6e5f52796..88fede3a584dc58aa186569188c3b234d146880e 100644 --- a/browserid/static/dialog/controllers/dialog_controller.js +++ b/resources/static/dialog/controllers/dialog_controller.js @@ -45,23 +45,38 @@ var bid = BrowserID, user = bid.User, errors = bid.Errors, - offline = false; + offline = false, + win = window; + PageController.extend("Dialog", {}, { - init: function(el) { + init: function(el, options) { + offline = false; + + options = options || {}; + + if(options.window) { + win = options.window; + } + var self=this; - //this.element.show(); // keep track of where we are and what we do on success and error self.onsuccess = null; self.onerror = null; - setupChannel(self); - self.stateMachine(); + + try { + win.setupChannel(self); + self.stateMachine(); + } catch (e) { + self.renderError("error.ejs", { + action: errors.relaySetup + }); + } }, getVerifiedEmail: function(origin_url, onsuccess, onerror) { var self=this; - self.onsuccess = onsuccess; self.onerror = onerror; @@ -71,13 +86,12 @@ } user.setOrigin(origin_url); - - // get the cleaned origin. $("#sitename").text(user.getHostname()); self.doCheckAuth(); - $(window).bind("unload", function() { + $(win).bind("unload", function() { + bid.Storage.setStagedOnBehalfOf(""); self.doCancel(); }); }, @@ -87,7 +101,6 @@ var self=this, hub = OpenAjax.hub, el = this.element; - hub.subscribe("offline", function(msg, info) { self.doOffline(); @@ -118,7 +131,7 @@ }); hub.subscribe("assertion_generated", function(msg, info) { - if(info.assertion !== null) { + if (info.assertion !== null) { self.doAssertionGenerated(info.assertion); } else { @@ -157,12 +170,16 @@ }, doOffline: function() { - this.renderError(errors.offline); + this.renderError("offline.ejs", {}); offline = true; }, doXHRError: function(info) { - if (!offline) this.renderError(errors.offline); + if (!offline) { + this.renderError("error.ejs", $.extend({ + action: errors.xhrError + }, info)); + } }, doConfirmUser: function(email) { @@ -177,13 +194,18 @@ doCancel: function() { var self=this; - if(self.onsuccess) { + if (self.onsuccess) { self.onsuccess(null); } }, doPickEmail: function() { - this.element.pickemail(); + this.element.pickemail({ + // XXX ideal is to get rid of this and have a User function + // that takes care of getting email addresses AND the last used email + // for this site. + origin: user.getHostname() + }); }, doAuthenticate: function(info) { diff --git a/browserid/static/dialog/controllers/page_controller.js b/resources/static/dialog/controllers/page_controller.js similarity index 83% rename from browserid/static/dialog/controllers/page_controller.js rename to resources/static/dialog/controllers/page_controller.js index ba80f9bbff4ee3569571b92843c422b76aa2c0e7..70113e8ad0d3165f32734415893fd530125303a6 100644 --- a/browserid/static/dialog/controllers/page_controller.js +++ b/resources/static/dialog/controllers/page_controller.js @@ -48,6 +48,8 @@ var me=this, bodyTemplate = options.bodyTemplate, bodyVars = options.bodyVars, + errorTemplate = options.errorTemplate, + errorVars = options.errorVars, waitTemplate = options.waitTemplate, waitVars = options.waitVars; @@ -60,6 +62,10 @@ me.renderWait(waitTemplate, waitVars); } + if(errorTemplate) { + me.renderError(errorTemplate, errorVars); + } + // XXX move all of these, bleck. $("form").bind("submit", me.onSubmit.bind(me)); $("#cancel").click(me.onCancel.bind(me)); @@ -99,10 +105,20 @@ $("#wait").stop().hide().fadeIn(ANIMATION_TIME); }, - renderError: function(error_vars) { - this.renderTemplates("#error", "wait.ejs", error_vars); - $("body").removeClass("waiting").removeClass("form").addClass("error").css('opacity', 1); - $("#error").stop().hide().fadeIn(ANIMATION_TIME); + renderError: function(body, body_vars) { + this.renderTemplates("#error", body, body_vars); + $("body").removeClass("waiting").removeClass("form").addClass("error"); + $("#error").stop().css('opacity', 1).hide().fadeIn(ANIMATION_TIME); + + /** + * What a big steaming pile, use CSS animations for this! + */ + $("#openMoreInfo").click(function(event) { + event.preventDefault(); + + $("#moreInfo").slideDown(); + $("#openMoreInfo").css({visibility: "hidden"}); + }); }, onSubmit: function(event) { @@ -139,12 +155,16 @@ /** * Get a curried function to an error dialog. * @method getErrorDialog - * @method {object} info - info to use for the error dialog. Should have + * @method {object} action - info to use for the error dialog. Should have * two fields, message, description. */ - getErrorDialog: function(info) { + getErrorDialog: function(action) { var self=this; - return self.renderError.bind(self, info); + return function(lowLevelInfo) { + self.renderError("error.ejs", $.extend({ + action: action + }, lowLevelInfo)); + } }, onCancel: function(event) { diff --git a/browserid/static/dialog/controllers/pickemail_controller.js b/resources/static/dialog/controllers/pickemail_controller.js similarity index 92% rename from browserid/static/dialog/controllers/pickemail_controller.js rename to resources/static/dialog/controllers/pickemail_controller.js index 1b3679c132953e12afd296df960419aa5a3cb945..a9197a4c60c03029d16fe39de02c24c67f4c0815 100644 --- a/browserid/static/dialog/controllers/pickemail_controller.js +++ b/resources/static/dialog/controllers/pickemail_controller.js @@ -41,6 +41,8 @@ bid = BrowserID, user = bid.User, errors = bid.Errors, + storage = bid.Storage, + origin, body = $("body"), animationComplete = body.innerWidth() < 640, assertion; @@ -109,6 +111,9 @@ // the animation, hopefully this minimizes the delay the user notices. var self=this; user.getAssertion(email, function(assert) { + // XXX make a user api call that gets the assertion and sets the site + // email as well. + storage.setSiteEmail(origin, email); assertion = assert || null; startAnimation.call(self); }, self.getErrorDialog(errors.getAssertion)); @@ -176,10 +181,16 @@ PageController.extend("Pickemail", {}, { init: function(el, options) { + origin = options.origin; + this._super(el, { bodyTemplate: "pickemail.ejs", bodyVars: { - identities: user.getStoredEmailKeypairs() + identities: user.getStoredEmailKeypairs(), + // XXX ideal is to get rid of this and have a User function + // that takes care of getting email addresses AND the last used email + // for this site. + siteemail: storage.getSiteEmail(options.origin) } }); diff --git a/browserid/static/dialog/css/m.css b/resources/static/dialog/css/m.css similarity index 80% rename from browserid/static/dialog/css/m.css rename to resources/static/dialog/css/m.css index 49f4a561375d7648adab479f5d104dab69b0c9bb..303557e63d05233aeb9c500c04c35878a6b64f2e 100644 --- a/browserid/static/dialog/css/m.css +++ b/resources/static/dialog/css/m.css @@ -78,7 +78,7 @@ background-color: transparent; } - .error #formWrap, .waiting #formWrap { + .error #formWrap, .error #wait, .waiting #formWrap { display: none; } @@ -102,15 +102,29 @@ font-size: 14px; } - #content, .pickemail .form_section, .pickemail .inputs, .vertical { + #content, .form_section, .pickemail .inputs, .vertical { height: auto; overflow: visible; } - #wait .vertical { + #error .vertical, #wait .vertical { height: 250px; } -} + #error .vertical { + width: auto; + } + + #error .vertical > div { + display: block; + height: auto; + padding: 10px; + } + + #error #borderbox { + border-left: none; + padding: 0; + } + diff --git a/browserid/static/dialog/css/popup.css b/resources/static/dialog/css/popup.css similarity index 93% rename from browserid/static/dialog/css/popup.css rename to resources/static/dialog/css/popup.css index b1b54243f2d023bc6e2a46f27d7a621ad3b1ba01..fb43054b98e7ff5cbcac496726a0ffd0f5378cc1 100644 --- a/browserid/static/dialog/css/popup.css +++ b/resources/static/dialog/css/popup.css @@ -118,16 +118,21 @@ section > .contents { #wait, #error { text-align: center; - background-image: url("/i/bg.png"); } #wait { z-index: 1; + background-image: url("/i/bg.png"); } #error { display: none; z-index: 2; + background-color: #fff; +} + +#error ul, #error li { + list-style-type: none; } #wait strong, #error strong { @@ -135,10 +140,44 @@ section > .contents { font-weight: bold; } -#error { - z-index: 2; + +#error.unsupported .vertical { + width: 630px; + margin: 0 auto; + display: block; +} + + +#error.unsupported .vertical > div { + display: table-cell; + vertical-align: middle; + padding: 0 10px; + height: 250px; +} + +#error #moreInfo { + display: none; } +#error a { + color: #549FDC; + text-decoration: underline; +} + +#error #borderbox { + border-left: 1px solid #777; + padding: 20px 0; +} + +#error #borderbox img { + border: none; +} + +#error #alternative .lighter { + color: #777; +} + + #formWrap { background-color: #fff; background-image: none; @@ -269,6 +308,10 @@ label { color: #333; } +.inputs > li > label.preselected { + font-weight: bold; +} + .inputs > li:only-child > label { cursor: default; } @@ -521,7 +564,7 @@ html[xmlns] .cf { overflow-y: auto; } -.pickemail .form_section { +.form_section { height: 176px; } diff --git a/browserid/static/dialog/dialog.js b/resources/static/dialog/dialog.js similarity index 97% rename from browserid/static/dialog/dialog.js rename to resources/static/dialog/dialog.js index d3070184d61ce0bc8855bb5e552a55f8dbeae6f9..3c70f70d2a0ed2e7fc737f91fa9cd3fe3769c026 100644 --- a/browserid/static/dialog/dialog.js +++ b/resources/static/dialog/dialog.js @@ -68,7 +68,9 @@ steal.plugins( .views('authenticate.ejs', 'confirmemail.ejs', 'pickemail.ejs', - 'wait.ejs' + 'wait.ejs', + 'error.ejs', + 'offline.ejs' ). then(function() { diff --git a/browserid/static/dialog/funcunit.html b/resources/static/dialog/funcunit.html similarity index 100% rename from browserid/static/dialog/funcunit.html rename to resources/static/dialog/funcunit.html diff --git a/browserid/static/dialog/mozilla.png b/resources/static/dialog/mozilla.png similarity index 100% rename from browserid/static/dialog/mozilla.png rename to resources/static/dialog/mozilla.png diff --git a/resources/static/dialog/qunit.html b/resources/static/dialog/qunit.html new file mode 100644 index 0000000000000000000000000000000000000000..0236807217744451277741940817a7f083dcc48c --- /dev/null +++ b/resources/static/dialog/qunit.html @@ -0,0 +1,63 @@ +<html> + <head> + <link rel="stylesheet" type="text/css" href="/funcunit/qunit/qunit.css" /> + <title>dialog QUnit Test</title> + <script type='text/javascript' src='/vepbundle'></script> + <script type='text/javascript' src='/steal/steal.js?/dialog/test/qunit'></script> + </head> + <body> + + <h1 id="qunit-header">dialog Test Suite</h1> + <h2 id="qunit-banner"></h2> + <div id="qunit-testrunner-toolbar"></div> + <h2 id="qunit-userAgent"></h2> + <div id="test-content"> + </div> + <ol id="qunit-tests"></ol> + <div id="qunit-test-area"></div> + + <h3>Content below here is test content that can be ignored</h3> + + <div id="controller_head"> + + <div id="formWrap"> + <div class="contents"></div> + </div> + + <div id="wait"> + <div class="contents"></div> + </div> + + <div id="error"> + <div class="contents"></div> + </div> + + <input id="email" /> + <span id="cannotconfirm" class="error">Cannot confirm</span> + <span id="cannotcommunicate" class="error">Cannot communicate</span> + <span id="siteinfo" class="error"><span class="website"></span></span> + <span class=".hint">Hint</span> + </div> + + <div id="needsTooltip">Tooltip Anchor</div> + + <div id="shortTooltip" class="tooltip" for="needsTooltip"> + short tooltip + </div> + + <div id="longTooltip" class="tooltip" for="needsTooltip"> + This is a long tooltip. This should remain on the screen for about 5 seconds. + </div> + + <ul class="notifications"> + <li class="notification emailsent">Email Sent</li> + <li class="notification doh">doh</li> + </ul> + + <script type="text/html" id="templateTooltip"> + <div class="tooltip"> + {{ contents }} + </div> + </script> + </body> +</html> diff --git a/browserid/static/dialog/register_iframe.html b/resources/static/dialog/register_iframe.html similarity index 100% rename from browserid/static/dialog/register_iframe.html rename to resources/static/dialog/register_iframe.html diff --git a/browserid/static/dialog/register_iframe.js b/resources/static/dialog/register_iframe.js similarity index 100% rename from browserid/static/dialog/register_iframe.js rename to resources/static/dialog/register_iframe.js diff --git a/browserid/static/dialog/resources/base64.js b/resources/static/dialog/resources/base64.js similarity index 100% rename from browserid/static/dialog/resources/base64.js rename to resources/static/dialog/resources/base64.js diff --git a/resources/static/dialog/resources/browser-support.js b/resources/static/dialog/resources/browser-support.js new file mode 100644 index 0000000000000000000000000000000000000000..6c2e34818d0e67ec6b20ed76ce0f03ce3404593a --- /dev/null +++ b/resources/static/dialog/resources/browser-support.js @@ -0,0 +1,118 @@ +/*globals BrowserID: true */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla BrowserID. + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +BrowserID.BrowserSupport = (function() { + var bid = BrowserID, + win = window, + nav = navigator, + reason; + + // For unit testing + function setTestEnv(newNav, newWindow) { + nav = newNav; + win = newWindow; + } + + function getInternetExplorerVersion() { + var rv = -1; // Return value assumes failure. + if (nav.appName == 'Microsoft Internet Explorer') { + var ua = nav.userAgent; + var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})"); + if (re.exec(ua) != null) + rv = parseFloat(RegExp.$1); + } + + return rv; + } + + function checkIE() { + var ieVersion = getInternetExplorerVersion(), + ieNosupport = ieVersion > -1 && ieVersion < 9; + + if(ieNosupport) { + return "IE_VERSION"; + } + } + + function explicitNosupport() { + return checkIE(); + } + + function checkLocalStorage() { + var localStorage = 'localStorage' in win && win['localStorage'] !== null; + if(!localStorage) { + return "LOCALSTORAGE"; + } + } + + function checkPostMessage() { + if(!win.postMessage) { + return "POSTMESSAGE"; + } + } + + function isSupported() { + reason = checkLocalStorage() || checkPostMessage() || explicitNosupport(); + + return !reason; + } + + function getNoSupportReason() { + return reason; + } + + return { + /** + * Set the test environment. + * @method setTestEnv + */ + setTestEnv: setTestEnv, + /** + * Check whether the current browser is supported + * @method isSupported + * @returns {boolean} + */ + isSupported: isSupported, + /** + * Called after isSupported, if isSupported returns false. Gets the reason + * why browser is not supported. + * @method getNoSupportReason + * @returns {string} + */ + getNoSupportReason: getNoSupportReason + }; + +}()); + diff --git a/browserid/static/dialog/resources/browserid-extensions.js b/resources/static/dialog/resources/browserid-extensions.js similarity index 100% rename from browserid/static/dialog/resources/browserid-extensions.js rename to resources/static/dialog/resources/browserid-extensions.js diff --git a/browserid/static/dialog/resources/browserid.js b/resources/static/dialog/resources/browserid.js similarity index 100% rename from browserid/static/dialog/resources/browserid.js rename to resources/static/dialog/resources/browserid.js diff --git a/browserid/static/dialog/resources/channel.js b/resources/static/dialog/resources/channel.js similarity index 61% rename from browserid/static/dialog/resources/channel.js rename to resources/static/dialog/resources/channel.js index 0d52976bb22f8dd74c7599d74dff39609716189f..859d641b8b266a4bb6a9c5719c1cc3af5b893030 100644 --- a/browserid/static/dialog/resources/channel.js +++ b/resources/static/dialog/resources/channel.js @@ -1,5 +1,5 @@ /*jshint browsers:true, forin: true, laxbreak: true */ -/*global alert:true, setupNativeChannel:true, setupIFrameChannel:true*/ +/*global BrowserID: true*/ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * @@ -52,93 +52,95 @@ (function() { - function getRelayID() { - return window.location.href.slice(window.location.href.indexOf('#') + 1); - } + var win = window, + nav = navigator, + onCompleteCallback; function getRelayName() { - return "browserid_relay_" + getRelayID(); + var relayID = win.location.href.slice(win.location.href.indexOf('#') + 1); + return "browserid_relay_" + relayID; } function getRelayWindow() { - var frameWindow = window.opener.frames[getRelayName()]; + var frameWindow = win.opener.frames[getRelayName()]; return frameWindow; } - function registerWithRelayFrame(callback) { - var frameWindow = getRelayWindow(); - if (frameWindow) { - frameWindow['register_dialog'](callback); - } + function setupNativeChannel(controller) { + nav.id.channel.registerController(controller); } - function getRPRelay() { - var frameWindow = getRelayWindow(); - return frameWindow && frameWindow['browserid_relay']; - } + function setupIFrameChannel(controller) { + // TODO - Add a check for whether the dialog was opened by another window + // (has window.opener) as well as whether the relay function exists. + // If these conditions are not met, then print an appropriate message. + function onsuccess(rv) { + onCompleteCallback(rv, null); + } - function errorOut(trans, code) { - function getVerboseMessage(code) { - var msgs = { - "canceled": "user canceled selection", - "notImplemented": "the user tried to invoke behavior that's not yet implemented", - "serverError": "a technical problem was encountered while trying to communicate with BrowserID servers." - }; - var msg = msgs[code]; - if (!msg) { - alert("need verbose message for " + code); - msg = "unknown error"; - } - return msg; + function onerror(error) { + onCompleteCallback(null, error); } - trans.error(code, getVerboseMessage(code)); - window.self.close(); - } + // The relay frame will give us the origin and a function to call when + // dialog processing is complete. + var frameWindow = getRelayWindow(); + if (frameWindow) { + frameWindow.BrowserID.Relay.registerClient(function(origin, onComplete) { + onCompleteCallback = onComplete; + controller.getVerifiedEmail(origin, onsuccess, onerror); + }); + win.location.hash = ''; + } + else { + throw "relay frame not found"; + } + } - window.setupChannel = function(controller) { - if (navigator.id && navigator.id.channel) + function open(controller) { + if (nav.id && nav.id.channel) setupNativeChannel(controller); else setupIFrameChannel(controller); - }; + } - var setupNativeChannel = function(controller) { - navigator.id.channel.registerController(controller); - }; - var setupIFrameChannel = function(controller) { - // TODO - Add a check for whether the dialog was opened by another window - // (has window.opener) as well as whether the relay function exists. - // If these conditions are not met, then print an appropriate message. + function init(options) { + onCompleteCallback = undefined; - // get the relay here at the time the channel is setup before any navigation has - // occured. if we wait the window hash might change as a side effect to user - // navigation, which would cause us to not find our parent window. - // issue #295 - var relay = getRPRelay(); - - function onsuccess(rv) { - // Get the relay here so that we ensure that the calling window is still - // open and we aren't causing a problem. - if (relay) { - relay(rv, null); - } + if(options.navigator) { + nav = navigator; } - function onerror(error) { - if (relay) { - relay(null, error); - } + if(options.window) { + win = options.window; } + } - // The relay frame will give us the origin. - registerWithRelayFrame(function(origin) { - controller.getVerifiedEmail(origin, onsuccess, onerror); - }); - window.location.hash = ''; - }; + if(window.BrowserID) { + BrowserID.Channel = { + /** + * Used to intialize the channel, mostly for unit testing to override + * window and navigator. + * @method init + */ + init: init, + + /** + * Open the channel. + * @method open + * @param {object} options - contains: + * * options.getVerifiedEmail {function} - function to /get + */ + open: open + }; + } + /** + * This is here as a legacy API for addons/etc that are depending on + * window.setupChannel; + */ + window.setupChannel = open; }()); diff --git a/browserid/static/relay/relay.js b/resources/static/dialog/resources/error-messages.js similarity index 61% rename from browserid/static/relay/relay.js rename to resources/static/dialog/resources/error-messages.js index 46212cc2479faef04dc09da5e978b792bbc7c67a..3f6aeefc37aaea3b8209523edef1df81bd182377 100644 --- a/browserid/static/relay/relay.js +++ b/resources/static/dialog/resources/error-messages.js @@ -1,5 +1,4 @@ -/*global Channel: true, errorOut: true */ - +/*global BrowserID: true*/ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * @@ -34,50 +33,72 @@ * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ +BrowserID.Errors = (function(){ + "use strict"; + var Errors = { + authenticate: { + title: "Authenticating User" + }, -(function() { - "use strict"; + addEmail: { + title: "Adding Address" + }, - window.console = window.console || { - log: function() {} - }; + checkAuthentication: { + title: "Checking Authentication" + }, - var ipServer = "https://browserid.org", - transaction, - origin, - chan = Channel.build( { - window: window.parent, - origin: "*", - scope: "mozid" - } ); + createUser: { + title: "Creating Account" + }, + getAssertion: { + title: "Getting Assertion" + }, - window.register_dialog = function(callback) { - // register the dialog, tell the dialog what the origin is. - // Get the origin from the channel binding. - callback(origin); - }; + isEmailRegistered: { + title: "Checking Email Address" + }, - window.browserid_relay = function(status, error) { - if(error) { - errorOut(transaction, error); - } - else { - try { - transaction.complete(status); - } catch(e) { - // The relay function is called a second time after the - // initial success, when the window is closing. - } - } - }; + logoutUser: { + title: "Logout Failed" + }, + + offline: { + title: "You are offline!", + message: "Unfortunately, BrowserID cannot communicate while offline!" + }, + + registration: { + title: "Registration Failed" + }, - chan.bind("getVerifiedEmail", function(trans, s) { - origin = trans.origin; - trans.delayReturn(true); + relaySetup: { + title: "Establishing Relay", + message: "Relay frame could not be found" + }, - transaction = trans; - }); + requestPasswordReset: { + title: "Resetting Password" + }, + signIn: { + title: "Signin Failed" + }, + + syncAddress: { + title: "Syncing Address" + }, + + xhrError: { + title: "Communication Error" + } + + }; + + + return Errors; }()); + + diff --git a/browserid/static/dialog/resources/jschannel.js b/resources/static/dialog/resources/jschannel.js similarity index 100% rename from browserid/static/dialog/resources/jschannel.js rename to resources/static/dialog/resources/jschannel.js diff --git a/browserid/static/dialog/resources/network.js b/resources/static/dialog/resources/network.js similarity index 91% rename from browserid/static/dialog/resources/network.js rename to resources/static/dialog/resources/network.js index b447aa2f08353db38d5f928a4f9b66154e25591b..ea1f53b312e0e9c1c9c4a3a7c6351386996c71c1 100644 --- a/browserid/static/dialog/resources/network.js +++ b/resources/static/dialog/resources/network.js @@ -37,8 +37,7 @@ BrowserID.Network = (function() { "use strict"; - var XHR_TIMEOUT = 10000, - csrf_token, + var csrf_token, xhr = $, server_time, auth_status, @@ -55,10 +54,17 @@ BrowserID.Network = (function() { } } - function xhrError(cb, errorMessage) { - return function() { - if (cb) cb(); - hub && hub.publish("xhrError", errorMessage); + function xhrError(cb, info) { + return function(jqXHR, textStatus, errorThrown) { + info = info || {}; + var network = info.network = info.network || {}; + + network.status = jqXHR && jqXHR.status; + network.textStatus = textStatus; + network.errorThrown = errorThrown; + + if (cb) cb(info); + hub && hub.publish("xhrError", info); }; } @@ -70,9 +76,13 @@ BrowserID.Network = (function() { // that are thrown in the response handlers and it becomes very difficult // to debug. success: deferResponse(options.success), - error: deferResponse(xhrError(options.error, options.errorMessage)), - dataType: "json", - timeout: XHR_TIMEOUT + error: deferResponse(xhrError(options.error, { + network: { + type: "GET", + url: options.url + } + })), + dataType: "json" }); } @@ -92,8 +102,13 @@ BrowserID.Network = (function() { // that are thrown in the response handlers and it becomes very difficult // to debug. success: deferResponse(options.success), - error: deferResponse(xhrError(options.error, options.errorMessage)), - timeout: XHR_TIMEOUT + error: deferResponse(xhrError(options.error, { + network: { + type: "POST", + url: options.url, + data: options.data + } + })) }); }, options.error); } @@ -101,6 +116,7 @@ BrowserID.Network = (function() { function withContext(cb, onFailure) { if (typeof auth_status === 'boolean' && typeof csrf_token !== 'undefined') cb(); else { + var url = "/wsapi/session_context"; xhr.ajax({ url: "/wsapi/session_context", success: function(result) { @@ -112,8 +128,12 @@ BrowserID.Network = (function() { auth_status = result.authenticated; cb(); }, - error: deferResponse(xhrError(onFailure)), - timeout: XHR_TIMEOUT + error: deferResponse(xhrError(onFailure, { + network: { + type: "GET", + url: url + } + })) }); } } @@ -233,7 +253,13 @@ BrowserID.Network = (function() { success: function(status) { if (onSuccess) onSuccess(status.success); }, - error: onFailure + error: function(info) { + // 403 is throttling. + if(info.network.status === 403) { + if (onSuccess) onSuccess(false); + } + else if (onFailure) onFailure(info); + } }); }, @@ -391,7 +417,13 @@ BrowserID.Network = (function() { success: function(status) { if (onSuccess) onSuccess(status.success); }, - error: onFailure + error: function(info) { + // 403 is throttling. + if(info.network.status === 403) { + if (onSuccess) onSuccess(false); + } + else if (onFailure) onFailure(info); + } }); }, diff --git a/browserid/static/dialog/resources/storage.js b/resources/static/dialog/resources/storage.js similarity index 100% rename from browserid/static/dialog/resources/storage.js rename to resources/static/dialog/resources/storage.js diff --git a/browserid/static/dialog/resources/tooltip.js b/resources/static/dialog/resources/tooltip.js similarity index 81% rename from browserid/static/dialog/resources/tooltip.js rename to resources/static/dialog/resources/tooltip.js index 8625595e44fb8dd760e8e63ce44d9dc671e53118..281e664db3d1c0d4884b71e0614c6d178e53cd13 100644 --- a/browserid/static/dialog/resources/tooltip.js +++ b/resources/static/dialog/resources/tooltip.js @@ -39,7 +39,8 @@ BrowserID.Tooltip = (function() { "use strict"; var ANIMATION_TIME = 250, - TOOLTIP_DISPLAY = 2000; + TOOLTIP_DISPLAY = 2000, + READ_WPM = 200; function createTooltip(el) { var contents = el.html(); @@ -63,14 +64,21 @@ BrowserID.Tooltip = (function() { } function animateTooltip(el, complete) { + var contents = el.text().replace(/\s+/, ' ').replace(/^\s+/, '').replace(/\s+$/, ''); + var words = contents.split(' ').length; + + // The average person can read ± 250 wpm. + var wordTimeMS = (words / READ_WPM) * 60 * 1000; + var displayTimeMS = Math.max(wordTimeMS, TOOLTIP_DISPLAY); + el.fadeIn(ANIMATION_TIME, function() { setTimeout(function() { el.fadeOut(ANIMATION_TIME, complete); - }, TOOLTIP_DISPLAY); + }, displayTimeMS); }); } - function createAndShowRelatedTooltip(el, relatedTo) { + function createAndShowRelatedTooltip(el, relatedTo, complete) { // This means create a copy of the tooltip element and position it in // relation to an element. Right now we are putting the tooltip directly // above the element. Once the tooltip is no longer needed, remove it @@ -81,22 +89,26 @@ BrowserID.Tooltip = (function() { positionTooltip(tooltip, target); animateTooltip(tooltip, function() { - tooltip.remove(); - tooltip = null; + console.log("close tooltip"); + if (tooltip) { + tooltip.remove(); + tooltip = null; + } + if (complete) complete(); }); } - function showTooltip(el) { + function showTooltip(el, complete) { el = $(el); var messageFor = el.attr("for"); // First, see if we are "for" another element, if we are, create a copy of // the tooltip to attach to the element. if(messageFor) { - createAndShowRelatedTooltip(el, messageFor); + createAndShowRelatedTooltip(el, messageFor, complete); } else { - animateTooltip(el); + animateTooltip(el, complete); } } diff --git a/browserid/static/dialog/resources/underscore-min.js b/resources/static/dialog/resources/underscore-min.js similarity index 100% rename from browserid/static/dialog/resources/underscore-min.js rename to resources/static/dialog/resources/underscore-min.js diff --git a/browserid/static/dialog/resources/user.js b/resources/static/dialog/resources/user.js similarity index 88% rename from browserid/static/dialog/resources/user.js rename to resources/static/dialog/resources/user.js index fb6419addb05d1cd477065d8d22895ab15fc468d..a1541ffbdc0f35ba165c6cb15603066f6a3cb152 100644 --- a/browserid/static/dialog/resources/user.js +++ b/resources/static/dialog/resources/user.js @@ -108,6 +108,10 @@ BrowserID.User = (function() { // 'mustAuth' - user must authenticate // 'noRegistration' - no registration is in progress if (status === "complete" || status === "mustAuth") { + // As soon as the registration comes back as complete, we should + // ensure that the stagedOnBehalfOf is cleared so there is no stale + // data. + storage.setStagedOnBehalfOf(""); if (onSuccess) { onSuccess(status); } @@ -232,11 +236,7 @@ BrowserID.User = (function() { // remember this for later storage.setStagedOnBehalfOf(self.getHostname()); - network.createUser(email, origin, function(created) { - if (onSuccess) { - onSuccess(created); - } - }, onFailure); + network.createUser(email, origin, onSuccess, onFailure); }, /** @@ -250,40 +250,87 @@ BrowserID.User = (function() { registrationPoll(network.checkUserRegistration, email, onSuccess, onFailure); }, + /** + * Verify a user + * @method verifyUser + * @param {string} token - token to verify. + * @param {string} password - password to set for account. + * @param {function} [onSuccess] - Called to give status updates. + * @param {function} [onFailure] - Called on error. + */ + verifyUser: function(token, password, onSuccess, onFailure) { + network.emailForVerificationToken(token, function (email) { + var invalidInfo = { valid: false }; + if (email) { + network.completeUserRegistration(token, password, function (valid) { + var info = valid ? { + valid: valid, + email: email, + origin: storage.getStagedOnBehalfOf() + } : invalidInfo; + + storage.setStagedOnBehalfOf(""); + + if (onSuccess) onSuccess(info); + }, onFailure); + } else if(onSuccess) { + onSuccess(invalidInfo); + } + }, onFailure); + }, + /** * Set the password of the current user. * @method setPassword * @param {string} password - password to set - * @param {function} [onSuccess] - Called on successful completion. + * @param {function} [onComplete] - Called on successful completion. * @param {function} [onFailure] - Called on error. */ - setPassword: function(password, onSuccess, onFailure) { - network.setPassword(password, onSuccess, onFailure); + setPassword: function(password, onComplete, onFailure) { + network.setPassword(password, onComplete, onFailure); }, /** * Request a password reset for the given email address. * @method requestPasswordReset * @param {string} email - email address to reset password for. - * @param {function} [onSuccess] - Callback to call when complete. + * @param {function} [onComplete] - Callback to call when complete, called + * with a single object, info. + * info.status {boolean} - true or false whether request was successful. + * info.reason {string} - if status false, reason of failure. * @param {function} [onFailure] - Called on XHR failure. */ - requestPasswordReset: function(email, onSuccess, onFailure) { - network.requestPasswordReset(email, origin, onSuccess, onFailure); + requestPasswordReset: function(email, onComplete, onFailure) { + this.isEmailRegistered(email, function(registered) { + if (registered) { + network.requestPasswordReset(email, origin, function(reset) { + var status = { + success: reset + }; + + if(!reset) status.reason = "throttle"; + + if (onComplete) onComplete(status); + }, onFailure); + } + else if (onComplete) { + onComplete({ success: false, reason: "invalid_user" }); + } + }, onFailure); }, /** * Cancel the current user's account. Remove last traces of their * identity. * @method cancelUser - * @param {function} [onSuccess] - Called whenever complete. + * @param {function} [onComplete] - Called whenever complete. * @param {function} [onFailure] - called on error. */ - cancelUser: function(onSuccess, onFailure) { + cancelUser: function(onComplete, onFailure) { network.cancelUser(function() { setAuthenticationStatus(false); - if (onSuccess) { - onSuccess(); + if (onComplete) { + onComplete(); } }, onFailure); @@ -451,13 +498,10 @@ BrowserID.User = (function() { addEmail: function(email, onSuccess, onFailure) { var self = this; network.addEmail(email, origin, function(added) { - if (added) { - storage.setStagedOnBehalfOf(self.getHostname()); - // we no longer send the keypair, since we will certify it later. - if (onSuccess) { - onSuccess(added); - } - } + if (added) storage.setStagedOnBehalfOf(self.getHostname()); + + // we no longer send the keypair, since we will certify it later. + if (onSuccess) onSuccess(added); }, onFailure); }, diff --git a/browserid/static/dialog/resources/validation.js b/resources/static/dialog/resources/validation.js similarity index 90% rename from browserid/static/dialog/resources/validation.js rename to resources/static/dialog/resources/validation.js index aa8b5603bb06f4bad93b3022175752a03333944a..15b98c1b7a6bf3b1837f97f8cec72ec541bb4dea 100644 --- a/browserid/static/dialog/resources/validation.js +++ b/resources/static/dialog/resources/validation.js @@ -41,7 +41,15 @@ BrowserID.Validation = (function() { // gotten from http://blog.gerv.net/2011/05/html5_email_address_regexp/ // changed the requirement that there must be a ldh-str because BrowserID // is only used on internet based networks. - return /^[\w.!#$%&'*+\-/=?\^`{|}~]+@[a-z0-9-]+(\.[a-z0-9-]+)+$/.test(address); + var parts = address.split("@"); + + return /^[\w.!#$%&'*+\-/=?\^`{|}~]+@[a-z0-9-]+(\.[a-z0-9-]+)+$/.test(address) + // total address allwed to be 254 bytes long + && address.length <= 254 + // local side only allowed to be 64 bytes long + && parts[0] && parts[0].length <= 64 + // domain side allowed to be up to 253 bytes long + && parts[1] && parts[1].length <= 253; }; diff --git a/browserid/static/dialog/resources/wait-messages.js b/resources/static/dialog/resources/wait-messages.js similarity index 100% rename from browserid/static/dialog/resources/wait-messages.js rename to resources/static/dialog/resources/wait-messages.js diff --git a/browserid/static/dialog/scripts/build.html b/resources/static/dialog/scripts/build.html similarity index 100% rename from browserid/static/dialog/scripts/build.html rename to resources/static/dialog/scripts/build.html diff --git a/browserid/static/dialog/scripts/build.js b/resources/static/dialog/scripts/build.js similarity index 100% rename from browserid/static/dialog/scripts/build.js rename to resources/static/dialog/scripts/build.js diff --git a/browserid/static/dialog/scripts/clean.js b/resources/static/dialog/scripts/clean.js similarity index 100% rename from browserid/static/dialog/scripts/clean.js rename to resources/static/dialog/scripts/clean.js diff --git a/browserid/static/dialog/scripts/docs.js b/resources/static/dialog/scripts/docs.js similarity index 100% rename from browserid/static/dialog/scripts/docs.js rename to resources/static/dialog/scripts/docs.js diff --git a/browserid/static/dialog/test/funcunit/dialog_test.js b/resources/static/dialog/test/funcunit/dialog_test.js similarity index 100% rename from browserid/static/dialog/test/funcunit/dialog_test.js rename to resources/static/dialog/test/funcunit/dialog_test.js diff --git a/browserid/static/dialog/test/funcunit/funcunit.js b/resources/static/dialog/test/funcunit/funcunit.js similarity index 100% rename from browserid/static/dialog/test/funcunit/funcunit.js rename to resources/static/dialog/test/funcunit/funcunit.js diff --git a/verifier/test/certassertion-test.js b/resources/static/dialog/test/qunit/controllers/authenticate_controller_unit_test.js similarity index 50% rename from verifier/test/certassertion-test.js rename to resources/static/dialog/test/qunit/controllers/authenticate_controller_unit_test.js index 24edbead4e003185e41a33d3f8b354f6d85eb992..0a40cf088dd586bcd81dfdbcf7ec65c98a7da1c2 100644 --- a/verifier/test/certassertion-test.js +++ b/resources/static/dialog/test/qunit/controllers/authenticate_controller_unit_test.js @@ -1,3 +1,5 @@ +/*jshint browsers:true, forin: true, laxbreak: true */ +/*global steal: true, test: true, start: true, stop: true, module: true, ok: true, equal: true, BrowserID:true */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * @@ -18,7 +20,6 @@ * the Initial Developer. All Rights Reserved. * * Contributor(s): - * Ben Adida <benadida@mozilla.com> * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -33,44 +34,61 @@ * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ +steal.plugins("jquery").then("/dialog/controllers/page_controller", "/dialog/controllers/authenticate_controller", function() { + "use strict"; -var vows = require("vows"), - assert = require("assert"), - certassertion = require("../lib/certassertion"), - jwk = require("jwcrypto/jwk"), - jwt = require("jwcrypto/jwt"), - jwcert = require("jwcrypto/jwcert"), - vep = require("jwcrypto/vep"), - events = require("events"); + var controller, + el = $("body"), + storage = BrowserID.Storage, + emailRegistered = false, + userCreated = true; -vows.describe('certassertion').addBatch({ - "generate and certify key + assertion" : { - topic: function() { - // generate a key - var root_kp = jwk.KeyPair.generate("RS", 64); - var user_kp = jwk.KeyPair.generate("RS", 64); - var cert = new jwcert.JWCert("fakeroot.com", new Date(), user_kp.publicKey, {email:"user@fakeroot.com"}).sign(root_kp.secretKey); - var assertion = new jwt.JWT(null, new Date(), "rp.com").sign(user_kp.secretKey); - - var self = this; - var bundle = vep.bundleCertsAndAssertion([cert],assertion); - - // verify it - certassertion.verify( - bundle, "rp.com", - function(email, audience, expires) { - self.callback({email:email, audience: audience, expires:expires}); - }, - function(msg) {}, - function(issuer, next) { - if (issuer == "fakeroot.com") - next(root_kp.publicKey); - else - next(null); - }); + var userMock = { + getHostname: function() { return "host"; }, + isEmailRegistered: function(email, onSuccess, onFailure) { + onSuccess(emailRegistered); }, - "is successful": function(res, err) { - assert.notEqual(res.email, null); + + createUser: function(email, onSuccess, onFailure) { + onSuccess(userCreated); } + }; + + function reset() { + el = $("#controller_head"); + el.find("#formWrap .contents").html(""); + el.find("#wait .contents").html(""); + el.find("#error .contents").html(""); + + emailRegistered = false; + userCreated = true; + + OpenAjax.hub.unsubscribe("user_staged"); } -}).export(module); \ No newline at end of file + + module("controllers/authenticate_controller", { + setup: function() { + reset(); + storage.clear(); + controller = el.authenticate({ user: userMock }).controller(); + }, + + teardown: function() { + if (controller) { + controller.destroy(); + } + reset(); + storage.clear(); + } + }); + + test("setting email address prefills address field", function() { + controller.destroy(); + $("#email").val(""); + controller = el.authenticate({ user: userMock, email: "testuser@testuser.com" }).controller(); + equal($("#email").val(), "testuser@testuser.com", "email prefilled"); + }); + + +}); + diff --git a/resources/static/dialog/test/qunit/controllers/dialog_controller_unit_test.js b/resources/static/dialog/test/qunit/controllers/dialog_controller_unit_test.js new file mode 100644 index 0000000000000000000000000000000000000000..47be43214a20d1386b239295b1d27d35b7305928 --- /dev/null +++ b/resources/static/dialog/test/qunit/controllers/dialog_controller_unit_test.js @@ -0,0 +1,122 @@ +/*jshint browsers:true, forin: true, laxbreak: true */ +/*global steal: true, test: true, start: true, stop: true, module: true, ok: true, equal: true, BrowserID:true */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla BrowserID. + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +steal.plugins("jquery").then("/dialog/controllers/page_controller", "/dialog/controllers/dialog_controller", function() { + "use strict"; + + var controller, + el, + channelError = false; + + function reset() { + el = $("#controller_head"); + el.find("#formWrap .contents").html(""); + el.find("#wait .contents").html(""); + el.find("#error .contents").html(""); + + channelError = false; + } + + function initController() { + controller = el.dialog({ + window: { + setupChannel: function() { + if (channelError) throw "Channel error"; + } + } + }).controller(); + } + + module("controllers/dialog_controller", { + setup: function() { + reset(); + initController(); + }, + + teardown: function() { + controller.destroy(); + reset(); + } + }); + + test("initialization with channel error", function() { + controller.destroy(); + reset(); + channelError = true; + + initController(); + + ok($("#error .contents").text().length, "contents have been written"); + }); + + test("doOffline", function() { + controller.doOffline(); + ok($("#error .contents").text().length, "contents have been written"); + ok($("#error #offline").text().length, "offline error message has been written"); + }); + + test("doXHRError while online, no network info given", function() { + controller.doXHRError(); + ok($("#error .contents").text().length, "contents have been written"); + ok($("#error #action").text().length, "action contents have been written"); + equal($("#error #network").text().length, 0, "no network contents to be written"); + }); + + test("doXHRError while online, network info given", function() { + controller.doXHRError({ + network: { + type: "POST", + url: "browserid.org/verify" + } + }); + ok($("#error .contents").text().length, "contents have been written"); + ok($("#error #action").text().length, "action contents have been written"); + ok($("#error #network").text().length, "network contents have been written"); + }); + + test("doXHRError while offline does not update contents", function() { + controller.doOffline(); + $("#error #action").remove(); + + controller.doXHRError(); + ok(!$("#error #action").text().length, "XHR error is not reported if the user is offline."); + }); + + test("window.unload causes setStagedOnBehalfOf data to be cleared", function() { + }); + +}); + diff --git a/browserid/static/dialog/test/qunit/controllers/page_controller_unit_test.js b/resources/static/dialog/test/qunit/controllers/page_controller_unit_test.js similarity index 73% rename from browserid/static/dialog/test/qunit/controllers/page_controller_unit_test.js rename to resources/static/dialog/test/qunit/controllers/page_controller_unit_test.js index fca829d5c917981be1272ff7ee2148d1f089fa5c..299b8ed000444c38d3d8e3cf13ad80ad89f6b6a4 100644 --- a/browserid/static/dialog/test/qunit/controllers/page_controller_unit_test.js +++ b/resources/static/dialog/test/qunit/controllers/page_controller_unit_test.js @@ -41,16 +41,21 @@ steal.plugins("jquery").then("/dialog/controllers/page_controller", function() { bodyTemplate = "testBodyTemplate.ejs", waitTemplate = "wait.ejs"; - module("PageController", { + function reset() { + el = $("#controller_head"); + el.find("#formWrap .contents").html(""); + el.find("#wait .contents").html(""); + el.find("#error .contents").html(""); + } + + module("/controllers/page_controller", { setup: function() { - el = $("#page_controller"); + reset(); }, teardown: function() { - el.find("#formWrap .contents").html(""); - el.find("#wait .contents").html(""); - el.find("#error .contents").html(""); controller.destroy(); + reset(); } }); @@ -76,10 +81,11 @@ steal.plugins("jquery").then("/dialog/controllers/page_controller", function() { var html = el.find("#formWrap .contents").html(); ok(html.length, "with template specified, form text is loaded"); +/* var input = el.find("input").eq(0); ok(input.is(":focus"), "make sure the first input is focused"); - +*/ html = el.find("#wait .contents").html(); equal(html, "", "with body template specified, wait text is not loaded"); }); @@ -100,6 +106,22 @@ steal.plugins("jquery").then("/dialog/controllers/page_controller", function() { ok(html.length, "with wait template specified, wait text is loaded"); }); + test("page controller with error template renders in #error .contents", function() { + controller = el.page({ + errorTemplate: waitTemplate, + errorVars: { + title: "Test title", + message: "Test message" + } + }).controller(); + + var html = el.find("#formWrap .contents").html(); + equal(html, "", "with error template specified, form is ignored"); + + html = el.find("#error .contents").html(); + ok(html.length, "with error template specified, error text is loaded"); + }); + test("renderError renders an error message", function() { controller = el.page({ waitTemplate: waitTemplate, @@ -109,7 +131,7 @@ steal.plugins("jquery").then("/dialog/controllers/page_controller", function() { } }).controller(); - controller.renderError({ + controller.renderError("wait.ejs", { title: "error title", message: "error message" }); @@ -119,6 +141,37 @@ steal.plugins("jquery").then("/dialog/controllers/page_controller", function() { ok(html.length, "with error template specified, error text is loaded"); }); + test("renderError allows us to open expanded error info", function() { + controller = el.page({ + waitTemplate: waitTemplate, + waitVars: { + title: "Test title", + message: "Test message" + } + }).controller(); + + controller.renderError("error.ejs", { + action: { + title: "expanded action info", + message: "expanded message" + } + }); + + var html = el.find("#error .contents").html(); + + $("#moreInfo").hide(); + + $("#openMoreInfo").click(); + + setTimeout(function() { + equal($("#showMoreInfo").is(":visible"), false, "button is not visible after clicking expanded info"); + equal($("#moreInfo").is(":visible"), true, "expanded error info is visible after clicking expanded info"); + start(); + }, 500); + + stop(); + }); + test("getErrorDialog gets a function that can be used to render an error message", function() { controller = el.page({ waitTemplate: waitTemplate, @@ -128,9 +181,10 @@ steal.plugins("jquery").then("/dialog/controllers/page_controller", function() { } }).controller(); + // This is the medium level info. var func = controller.getErrorDialog({ - title: "error title", - message: "error message" + title: "medium level info error title", + message: "medium level info error message" }); equal(typeof func, "function", "a function was returned from getErrorDialog"); diff --git a/resources/static/dialog/test/qunit/controllers/pickemail_controller_unit_test.js b/resources/static/dialog/test/qunit/controllers/pickemail_controller_unit_test.js new file mode 100644 index 0000000000000000000000000000000000000000..25730ca41e0bbbd536078a531591c22e5d843992 --- /dev/null +++ b/resources/static/dialog/test/qunit/controllers/pickemail_controller_unit_test.js @@ -0,0 +1,96 @@ +/*jshint browsers:true, forin: true, laxbreak: true */ +/*global steal: true, test: true, start: true, stop: true, module: true, ok: true, equal: true, BrowserID:true */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla BrowserID. + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +steal.plugins("jquery").then("/dialog/controllers/page_controller", "/dialog/controllers/pickemail_controller", function() { + "use strict"; + + var controller, + el = $("body"), + storage = BrowserID.Storage; + + function reset() { + el = $("#controller_head"); + el.find("#formWrap .contents").html(""); + el.find("#wait .contents").html(""); + el.find("#error .contents").html(""); + } + + module("controllers/pickemail_controller", { + setup: function() { + reset(); + storage.clear(); + }, + + teardown: function() { + if (controller) { + controller.destroy(); + } + reset(); + storage.clear(); + } + }); + + + test("pickemail controller with email associated with site", function() { + storage.addEmail("testuser@testuser.com", {priv: "priv", pub: "pub"}); + storage.addEmail("testuser2@testuser.com", {priv: "priv", pub: "pub"}); + storage.setSiteEmail("browserid.org", "testuser2@testuser.com"); + + controller = el.pickemail({origin: "browserid.org"}).controller(); + ok(controller, "controller created"); + + var radioButton = $("input[type=radio]").eq(1); + ok(radioButton.is(":checked"), "the email address we specified is checked"); + + var label = radioButton.parent();; + ok(label.hasClass("preselected"), "the label has the preselected class"); + }); + + test("pickemail controller without email associated with site", function() { + storage.addEmail("testuser@testuser.com", {priv: "priv", pub: "pub"}); + + controller = el.pickemail({origin: "browserid.org"}).controller(); + ok(controller, "controller created"); + + var radioButton = $("input[type=radio]").eq(0); + equal(radioButton.is(":checked"), false, "The email address is not checked"); + + var label = radioButton.parent(); + equal(label.hasClass("preselected"), false, "the label has no class"); + }); + +}); + diff --git a/browserid/static/dialog/test/qunit/dialog_test.js b/resources/static/dialog/test/qunit/dialog_test.js similarity index 100% rename from browserid/static/dialog/test/qunit/dialog_test.js rename to resources/static/dialog/test/qunit/dialog_test.js diff --git a/verifier/lib/httputils.js b/resources/static/dialog/test/qunit/include_unit_test.js similarity index 72% rename from verifier/lib/httputils.js rename to resources/static/dialog/test/qunit/include_unit_test.js index f062b02f9ed12fc519e18f3a41037e47eca2b408..815ecf82138ce58855bffdae94d8639568269a97 100644 --- a/verifier/lib/httputils.js +++ b/resources/static/dialog/test/qunit/include_unit_test.js @@ -1,3 +1,5 @@ +/*jshint browsers:true, forin: true, laxbreak: true */ +/*global steal: true, test: true, start: true, stop: true, module: true, ok: true, equal: true, BrowserID: true */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * @@ -32,34 +34,19 @@ * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ +steal.plugins("jquery", "funcunit/qunit").then("/include.js", function() { + "use strict"; -// various little utilities to make crafting boilerplate responses -// simple + module("include.js"); + + test("navigator.id is available", function() { + equal(typeof navigator.id, "object", "navigator.id namespace is available"); + }); -exports.fourOhFour = function(resp, reason) -{ - resp.writeHead(404, {"Content-Type": "text/plain"}); - resp.write("Not Found"); - if (reason) { - resp.write(": " + reason); - } - resp.end(); -}; + test("navigator.id.getVerifiedEmail is available", function() { + equal(typeof navigator.id.getVerifiedEmail, "function", "navigator.id.getVerifiedEmail is available"); + }); -exports.serverError = function(resp, reason) -{ - resp.writeHead(500, {"Content-Type": "text/plain"}); - if (reason) resp.write(reason); - resp.end(); -}; -exports.badRequest = function(resp, reason) -{ - resp.writeHead(400, {"Content-Type": "text/plain"}); - resp.write("Bad Request"); - if (reason) { - resp.write(": " + reason); - } - resp.end(); -}; +}); diff --git a/browserid/static/dialog/test/qunit/browserid_unit_test.js b/resources/static/dialog/test/qunit/js/browserid_unit_test.js similarity index 93% rename from browserid/static/dialog/test/qunit/browserid_unit_test.js rename to resources/static/dialog/test/qunit/js/browserid_unit_test.js index 5755fabc56122270993a990e6bea620d0c424eb5..840039df59b43dd36e016f6c23636bddcc784d55 100644 --- a/browserid/static/dialog/test/qunit/browserid_unit_test.js +++ b/resources/static/dialog/test/qunit/js/browserid_unit_test.js @@ -34,10 +34,10 @@ * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ -steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/browserid", function() { +steal.plugins("jquery", "funcunit/qunit").then("/js/page_helpers", "/js/browserid", function() { "use strict"; - module("browserid-unit"); + module("/js/browserid"); }); diff --git a/resources/static/dialog/test/qunit/js/page_helpers_unit_test.js b/resources/static/dialog/test/qunit/js/page_helpers_unit_test.js new file mode 100644 index 0000000000000000000000000000000000000000..9f076596ea5b4691651f1e9fa7705923b556fc02 --- /dev/null +++ b/resources/static/dialog/test/qunit/js/page_helpers_unit_test.js @@ -0,0 +1,81 @@ +/*jshint browsers:true, forin: true, laxbreak: true */ +/*global steal: true, test: true, start: true, stop: true, module: true, ok: true, equal: true, BrowserID: true */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla BrowserID. + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/browserid", function() { + "use strict"; + + var pageHelpers = BrowserID.PageHelpers; + + module("/js/page_helpers"); + + + test("setStoredEmail/getStoredEmail/setupEmail prefills the email address", function() { + $("#email").val(""); + + pageHelpers.setStoredEmail("testuser@testuser.com"); + pageHelpers.setupEmail(); + + equal($("#email").val(), "testuser@testuser.com", "email was set on setupEmail"); + equal(pageHelpers.getStoredEmail(), "testuser@testuser.com", "getStoredEmail works correctly"); + }); + + test("a key press in the email address field saves it", function() { + $("#email").val(""); + + pageHelpers.setStoredEmail("testuser@testuser.co"); + pageHelpers.setupEmail(); + + // The fake jQuery event does not actually cause the letter to be added, we + // have to do that manually. + $("#email").val("testuser@testuser.com"); + + var e = jQuery.Event("keyup"); + e.which = 77; //choose the one you want + e.keyCode = 77; + $("#email").trigger(e); + + equal(pageHelpers.getStoredEmail(), "testuser@testuser.com", "hitting a key updates the stored email"); + }); + + test("clearStoredEmail clears the email address from storage", function() { + pageHelpers.clearStoredEmail(); + + equal(pageHelpers.getStoredEmail(), "", "clearStoredEmail clears stored email"); + }); + +}); + + diff --git a/verifier/run.js b/resources/static/dialog/test/qunit/mocks/mocks.js old mode 100755 new mode 100644 similarity index 78% rename from verifier/run.js rename to resources/static/dialog/test/qunit/mocks/mocks.js index 3c93845cef5995e116f6cf0e3937f0c835f124f3..70a00e1ffc8e09b63fc58f6d70ea3fe1d21e82b4 --- a/verifier/run.js +++ b/resources/static/dialog/test/qunit/mocks/mocks.js @@ -1,5 +1,6 @@ -#!/usr/bin/env node +/*jshint browsers:true, forin: true, laxbreak: true */ +/*global BrowserID: true */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * @@ -34,20 +35,5 @@ * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ +BrowserID.Mocks = {}; -var sys = require("sys"), - path = require("path"), - fs = require("fs"), - express = require("express"); - -var PRIMARY_HOST = "127.0.0.1"; -var PRIMARY_PORT = 62800; - -var handler = require("./app.js"); - -var app = express.createServer(); - -// let the specific server interact directly with the express server to register their middleware -if (handler.setup) handler.setup(app); - -app.listen(PRIMARY_PORT, PRIMARY_HOST); diff --git a/resources/static/dialog/test/qunit/mocks/xhr.js b/resources/static/dialog/test/qunit/mocks/xhr.js new file mode 100644 index 0000000000000000000000000000000000000000..3c09c8cabc78adfae74fb6c98dd6106dd98ab1fd --- /dev/null +++ b/resources/static/dialog/test/qunit/mocks/xhr.js @@ -0,0 +1,162 @@ +/*jshint browsers:true, forin: true, laxbreak: true */ +/*global wrappedAsyncTest: true, start: true, stop: true, module: true, ok: true, equal: true, BrowserID: true */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla BrowserID. + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +BrowserID.Mocks.xhr = (function() { + var contextInfo = { + server_time: new Date().getTime(), + csrf_token: "csrf", + authenticated: false + }; + + // this cert is meaningless, but it has the right format + var random_cert = "eyJhbGciOiJSUzEyOCJ9.eyJpc3MiOiJpc3N1ZXIuY29tIiwiZXhwIjoxMzE2Njk1MzY3NzA3LCJwdWJsaWMta2V5Ijp7ImFsZ29yaXRobSI6IlJTIiwibiI6IjU2MDYzMDI4MDcwNDMyOTgyMzIyMDg3NDE4MTc2ODc2NzQ4MDcyMDM1NDgyODk4MzM0ODExMzY4NDA4NTI1NTk2MTk4MjUyNTE5MjY3MTA4MTMyNjA0MTk4MDA0NzkyODQ5MDc3ODY4OTUxOTA2MTcwODEyNTQwNzEzOTgyOTU0NjUzODEwNTM5OTQ5Mzg0NzEyNzczMzkwMjAwNzkxOTQ5NTY1OTAzNDM5NTIxNDI0OTA5NTc2ODMyNDE4ODkwODE5MjA0MzU0NzI5MjE3MjA3MzYwMTA1OTA2MDM5MDIzMjk5NTYxMzc0MDk4OTQyNzg5OTk2NzgwMTAyMDczMDcxNzYwODUyODQxMDY4OTg5ODYwNDAzNDMxNzM3NDgwMTgyNzI1ODUzODk5NzMzNzA2MDY5IiwiZSI6IjY1NTM3In0sInByaW5jaXBhbCI6eyJlbWFpbCI6InRlc3R1c2VyQHRlc3R1c2VyLmNvbSJ9fQ.aVIO470S_DkcaddQgFUXciGwq2F_MTdYOJtVnEYShni7I6mqBwK3fkdWShPEgLFWUSlVUtcy61FkDnq2G-6ikSx1fUZY7iBeSCOKYlh6Kj9v43JX-uhctRSB2pI17g09EUtvmb845EHUJuoowdBLmLa4DSTdZE-h4xUQ9MsY7Ik"; + + /** + * This is the results table, the keys are the request type, url, and + * a "selector" for testing. The right is the expected return value, already + * decoded. If a result is "undefined", the request's error handler will be + * called. + */ + var xhr = { + results: { + "get /wsapi/session_context valid": contextInfo, + "get /wsapi/session_context invalid": contextInfo, + // We are going to test for XHR failures for session_context using + // call to serverTime. We are going to use the flag contextAjaxError + "get /wsapi/session_context ajaxError": contextInfo, + "get /wsapi/session_context throttle": contextInfo, + "get /wsapi/session_context contextAjaxError": undefined, + "get /wsapi/email_for_token?token=token valid": { email: "testuser@testuser.com" }, + "get /wsapi/email_for_token?token=token invalid": { success: false }, + "post /wsapi/authenticate_user valid": { success: true }, + "post /wsapi/authenticate_user invalid": { success: false }, + "post /wsapi/authenticate_user ajaxError": undefined, + "post /wsapi/cert_key valid": random_cert, + "post /wsapi/cert_key invalid": undefined, + "post /wsapi/cert_key ajaxError": undefined, + "post /wsapi/complete_email_addition valid": { success: true }, + "post /wsapi/complete_email_addition invalid": { success: false }, + "post /wsapi/complete_email_addition ajaxError": undefined, + "post /wsapi/stage_user valid": { success: true }, + "post /wsapi/stage_user invalid": { success: false }, + "post /wsapi/stage_user throttle": 403, + "post /wsapi/stage_user ajaxError": undefined, + "get /wsapi/user_creation_status?email=registered%40testuser.com pending": { status: "pending" }, + "get /wsapi/user_creation_status?email=registered%40testuser.com complete": { status: "complete" }, + "get /wsapi/user_creation_status?email=registered%40testuser.com mustAuth": { status: "mustAuth" }, + "get /wsapi/user_creation_status?email=registered%40testuser.com noRegistration": { status: "noRegistration" }, + "get /wsapi/user_creation_status?email=registered%40testuser.com ajaxError": undefined, + "post /wsapi/complete_user_creation valid": { success: true }, + "post /wsapi/complete_user_creation invalid": { success: false }, + "post /wsapi/complete_user_creation ajaxError": undefined, + "post /wsapi/logout valid": { success: true }, + "post /wsapi/logout ajaxError": undefined, + "get /wsapi/have_email?email=registered%40testuser.com valid": { email_known: true }, + "get /wsapi/have_email?email=registered%40testuser.com throttle": { email_known: true }, + "get /wsapi/have_email?email=registered%40testuser.com ajaxError": undefined, + "get /wsapi/have_email?email=unregistered%40testuser.com valid": { email_known: false }, + "post /wsapi/remove_email valid": { success: true }, + "post /wsapi/remove_email invalid": { success: false }, + "post /wsapi/remove_email ajaxError": undefined, + "post /wsapi/account_cancel valid": { success: true }, + "post /wsapi/account_cancel invalid": { success: false }, + "post /wsapi/account_cancel ajaxError": undefined, + "post /wsapi/stage_email valid": { success: true }, + "post /wsapi/stage_email invalid": { success: false }, + "post /wsapi/stage_email throttle": 403, + "post /wsapi/stage_email ajaxError": undefined, + "post /wsapi/cert_key ajaxError": undefined, + "get /wsapi/email_addition_status?email=registered%40testuser.com pending": { status: "pending" }, + "get /wsapi/email_addition_status?email=registered%40testuser.com complete": { status: "complete" }, + "get /wsapi/email_addition_status?email=registered%40testuser.com mustAuth": { status: "mustAuth" }, + "get /wsapi/email_addition_status?email=registered%40testuser.com noRegistration": { status: "noRegistration" }, + "get /wsapi/email_addition_status?email=registered%40testuser.com ajaxError": undefined, + "get /wsapi/list_emails valid": {"testuser@testuser.com":{}}, + "get /wsapi/list_emails multiple": {"testuser@testuser.com":{}, "testuser2@testuser.com":{}}, + "get /wsapi/list_emails no_identities": [], + "get /wsapi/list_emails ajaxError": undefined + }, + + setContextInfo: function(field, value) { + contextInfo[field] = value; + }, + + useResult: function(result) { + xhr.resultType = result; + }, + + getLastRequest: function() { + return this.req; + }, + + ajax: function(obj) { + //console.log("ajax request"); + var type = obj.type ? obj.type.toLowerCase() : "get"; + + var req = this.req = { + type: type, + url: obj.url, + data: obj.data + }; + + + if(type === "post" && !obj.data.csrf) { + ok(false, "missing csrf token on POST request"); + } + + var resName = req.type + " " + req.url + " " + xhr.resultType; + var result = xhr.results[resName]; + + var type = typeof result; + if(!(type == "number" || type == "undefined")) { + if(obj.success) { + obj.success(result); + } + } + else if (obj.error) { + // Invalid result - either invalid URL, invalid GET/POST or + // invalid resultType + obj.error({ status: result || 400 }, "errorStatus", "errorThrown"); + } + } + }; + + + return xhr; +}()); + + diff --git a/browserid/static/dialog/test/qunit/pages/add_email_address_test.js b/resources/static/dialog/test/qunit/pages/add_email_address_test.js similarity index 63% rename from browserid/static/dialog/test/qunit/pages/add_email_address_test.js rename to resources/static/dialog/test/qunit/pages/add_email_address_test.js index 1d0d0a016d7f9b44515d643b18d6f6f0c53672d5..d353a848f9b27f4357411ab19c8dd400339453ae 100644 --- a/browserid/static/dialog/test/qunit/pages/add_email_address_test.js +++ b/resources/static/dialog/test/qunit/pages/add_email_address_test.js @@ -34,36 +34,24 @@ * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ -steal.plugins("jquery").then("/js/pages/add_email_address", function() { +steal.plugins("jquery").then("/dialog/test/qunit/mocks/xhr", "/dialog/resources/network", "/js/pages/add_email_address", function() { "use strict"; var bid = BrowserID, network = bid.Network, storage = bid.Storage, - emailForVerificationTokenFailure = false, - completeEmailRegistrationFailure = false, + xhr = bid.Mocks.xhr, validToken = true; - var netMock = { - emailForVerificationToken: function(token, onSuccess, onFailure) { - emailForVerificationTokenFailure ? onFailure() : onSuccess("testuser@testuser.com"); - }, - - completeEmailRegistration: function(token, onSuccess, onFailure) { - completeEmailRegistrationFailure ? onFailure() : onSuccess(validToken); - } - }; - module("pages/add_email_address", { setup: function() { - BrowserID.User.setNetwork(netMock); - emailForVerificationTokenFailure = completeEmailRegistrationFailure = false; - validToken = true; + network.setXHR(xhr); + xhr.useResult("valid"); $(".error").stop().hide(); $(".website").text(""); }, teardown: function() { - BrowserID.User.setNetwork(network); + network.setXHR($); $(".error").stop().hide(); $(".website").text(""); } @@ -74,39 +62,57 @@ steal.plugins("jquery").then("/js/pages/add_email_address", function() { bid.addEmailAddress("token"); - equal($("#email").text(), "testuser@testuser.com", "email set"); - ok($("#siteinfo").is(":visible"), "siteinfo is visible when we say what it is"); - equal($("#siteinfo .website").text(), "browserid.org", "origin is updated"); + setTimeout(function() { + equal($("#email").text(), "testuser@testuser.com", "email set"); + ok($("#siteinfo").is(":visible"), "siteinfo is visible when we say what it is"); + equal($("#siteinfo .website").text(), "browserid.org", "origin is updated"); + start(); + }, 500); + stop(); }); test("addEmailAddress with good token and nosite", function() { bid.addEmailAddress("token"); - equal($("#email").text(), "testuser@testuser.com", "email set"); - equal($("#siteinfo").is(":visible"), false, "siteinfo is not visible without having it"); - equal($("#siteinfo .website").text(), "", "origin is not updated"); + setTimeout(function() { + equal($("#email").text(), "testuser@testuser.com", "email set"); + equal($("#siteinfo").is(":visible"), false, "siteinfo is not visible without having it"); + equal($("#siteinfo .website").text(), "", "origin is not updated"); + start(); + }, 500); + stop(); }); test("addEmailAddress with bad token", function() { - validToken = false; + xhr.useResult("invalid"); bid.addEmailAddress("token"); - ok($("#cannotconfirm").is(":visible"), "cannot confirm box is visible"); + setTimeout(function() { + ok($("#cannotconfirm").is(":visible"), "cannot confirm box is visible"); + start(); + }, 500); + stop(); }); test("addEmailAddress with emailForVerficationToken XHR failure", function() { - validToken = true; - emailForVerificationTokenFailure = true; + xhr.useResult("ajaxError"); bid.addEmailAddress("token"); - ok($("#cannotcommunicate").is(":visible"), "cannot communicate box is visible"); + setTimeout(function() { + ok($("#cannotcommunicate").is(":visible"), "cannot communicate box is visible"); + start(); + }, 500); + stop(); }); test("addEmailAddress with completeEmailRegistration XHR failure", function() { - validToken = true; - completeEmailRegistrationFailure = true; + xhr.useResult("ajaxError"); bid.addEmailAddress("token"); - ok($("#cannotcommunicate").is(":visible"), "cannot communicate box is visible"); + setTimeout(function() { + ok($("#cannotcommunicate").is(":visible"), "cannot communicate box is visible"); + start(); + }, 500); + stop(); }); }); diff --git a/resources/static/dialog/test/qunit/pages/forgot_unit_test.js b/resources/static/dialog/test/qunit/pages/forgot_unit_test.js new file mode 100644 index 0000000000000000000000000000000000000000..ea45fed4411b9c4f4e2bcf001bc2c1724ee4286e --- /dev/null +++ b/resources/static/dialog/test/qunit/pages/forgot_unit_test.js @@ -0,0 +1,129 @@ +/*jshint browsers:true, forin: true, laxbreak: true */ +/*global steal: true, test: true, start: true, stop: true, module: true, ok: true, equal: true, BrowserID:true */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla BrowserID. + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +steal.plugins("jquery").then("/dialog/test/qunit/mocks/xhr", "/dialog/resources/network", "/dialog/resources/user", "/js/pages/forgot", function() { + "use strict"; + + var bid = BrowserID, + network = bid.Network, + user = bid.User, + xhr = bid.Mocks.xhr, + CHECK_DELAY = 500; + + module("pages/forgot", { + setup: function() { + network.setXHR(xhr); + $(".error").stop().hide(); + xhr.useResult("valid"); + bid.forgot(); + }, + teardown: function() { + network.setXHR($); + $(".error").stop().hide(); + $(".website").text(""); + bid.forgot.reset(); + } + }); + + test("requestPasswordReset with invalid email", function() { + $("#email").val("invalid"); + + xhr.useResult("invalid"); + bid.forgot.submit(); + + setTimeout(function() { + equal($(".emailsent").is(":visible"), false, "email not sent"); + start(); + }, CHECK_DELAY); + + stop(); + }); + + test("requestPasswordReset with known email", function() { + $("#email").val("registered@testuser.com"); + bid.forgot.submit(); + + setTimeout(function() { + ok($(".emailsent").is(":visible"), "email sent successfully"); + start(); + }, CHECK_DELAY); + + stop(); + }); + + test("requestPasswordReset with unknown email", function() { + $("#email").val("unregistered@testuser.com"); + bid.forgot.submit(); + + setTimeout(function() { + equal($(".emailsent").is(":visible"), false, "email not sent"); + start(); + }, CHECK_DELAY); + + stop(); + }); + + test("requestPasswordReset with throttling", function() { + xhr.useResult("throttle"); + + $("#email").val("throttled@testuser.com"); + bid.forgot.submit(); + + setTimeout(function() { + equal($(".emailsent").is(":visible"), false, "email not sent"); + start(); + }, CHECK_DELAY); + + stop(); + }); + + test("requestPasswordReset with XHR Error", function() { + xhr.useResult("ajaxError"); + + $("#email").val("testuser@testuser.com"); + bid.forgot.submit(); + + setTimeout(function() { + equal($(".emailsent").is(":visible"), false, "email not sent"); + equal($(".doh").is(":visible"), true, "XHR error message is displayed"); + start(); + }, CHECK_DELAY); + + stop(); + + }); + +}); diff --git a/resources/static/dialog/test/qunit/qunit.js b/resources/static/dialog/test/qunit/qunit.js new file mode 100644 index 0000000000000000000000000000000000000000..414860785aafa74b1a0bd39c40630e57ed9fb2d4 --- /dev/null +++ b/resources/static/dialog/test/qunit/qunit.js @@ -0,0 +1,39 @@ +steal("/dialog/resources/browserid.js", + "/dialog/resources/browser-support.js", + "/dialog/resources/error-messages.js", + "/dialog/resources/storage.js", + "/dialog/resources/tooltip.js", + "/dialog/resources/validation.js", + "/dialog/resources/underscore-min.js", + "/dialog/test/qunit/mocks/mocks.js", + "/dialog/test/qunit/mocks/xhr.js") + .plugins( + "jquery", + "jquery/controller", + "jquery/controller/subscribe", + "jquery/controller/view", + "jquery/view/ejs", + "funcunit/qunit") + .views('testBodyTemplate.ejs', + 'wait.ejs', + 'pickemail.ejs', + 'offline.ejs', + 'error.ejs') + .then("js/browserid_unit_test") + .then("js/page_helpers_unit_test") + .then("include_unit_test") + .then("relay/relay_unit_test") + .then("pages/add_email_address_test") + .then("pages/forgot_unit_test") + .then("resources/tooltip_unit_test") + .then("resources/channel_unit_test") + .then("resources/browser-support_unit_test") + .then("resources/validation_unit_test") + .then("resources/storage_unit_test") + .then("resources/network_unit_test") + .then("resources/user_unit_test") + .then("controllers/page_controller_unit_test") + .then("controllers/pickemail_controller_unit_test") + .then("controllers/dialog_controller_unit_test") + .then("controllers/authenticate_controller_unit_test") + diff --git a/resources/static/dialog/test/qunit/relay/relay_unit_test.js b/resources/static/dialog/test/qunit/relay/relay_unit_test.js new file mode 100644 index 0000000000000000000000000000000000000000..ae7e6dc6109e84fd1d7e6957d0e208a14218212f --- /dev/null +++ b/resources/static/dialog/test/qunit/relay/relay_unit_test.js @@ -0,0 +1,183 @@ +/*jshint browsers:true, forin: true, laxbreak: true */ +/*global steal: true, test: true, start: true, stop: true, module: true, ok: true, equal: true, BrowserID:true */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla BrowserID. + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +steal.plugins("jquery").then("/dialog/resources/jschannel", "/relay/relay", function() { + "use strict"; + + var winMock = {}, + relay = BrowserID.Relay; + + var channelMock = { + build: function(options) { + this.options = options; + + channelMock.bindMessage = channelMock.cb = channelMock.status = + channelMock.errorCode = channelMock.verboseError = undefined; + + return { + bind: function(message, cb) { + channelMock.bindMessage = message; + channelMock.cb = cb; + } + } + }, + + // Mock in the receiving of the RPC call from the RP. + receiveGetVerifiedEmail: function() { + // cb is the mock callback that is passed to Channel.bind + channelMock.cb({ + origin: "Origin", + delayReturn: function() {}, + complete: function(status) { + channelMock.status = status; + }, + error: function(code, verboseMessage) { + channelMock.errorCode = code; + channelMock.verboseError = verboseMessage; + } + }); + } + + }; + + + module("relay/relay", { + setup: function() { + relay.init({ + window: winMock, + channel: channelMock + }); + }, + teardown: function() { + relay.init({ + window: window.parent, + channel: Channel + }); + } + }); + + test("Can open the relay, happy case", function() { + relay.open(); + + /** + * Check to make sure channel build is correct + */ + equal(channelMock.options.window, winMock, "opening to the correct window"); + equal(channelMock.options.origin, "*", "accept messages from anybody"); + equal(channelMock.options.scope, "mozid", "mozid namespace"); + + /** + * Check to make sure the correct message is bound + */ + equal(channelMock.bindMessage, "getVerifiedEmail", "bound to getVerifiedEmail"); + }); + + test("channel.getVerifiedEmail before registerDialog", function() { + relay.open(); + + channelMock.receiveGetVerifiedEmail(); + + relay.registerClient(function(origin, completeCB) { + equal(origin, "Origin", "Origin set correctly"); + equal(typeof completeCB, "function", "A completion callback is specified"); + + start(); + }); + + stop(); + }); + + test("registerDialog before channel.getVerifiedEmail", function() { + relay.open(); + + relay.registerClient(function(origin, completeCB) { + equal(origin, "Origin", "Origin set correctly"); + equal(typeof completeCB, "function", "A completion callback is specified"); + + start(); + }); + + channelMock.receiveGetVerifiedEmail(); + + stop(); + }); + + test("calling the completeCB with assertion", function() { + relay.open(); + + channelMock.receiveGetVerifiedEmail(); + + relay.registerClient(function(origin, completeCB) { + completeCB("assertion", null); + equal(channelMock.status, "assertion", "channel gets the correct assertion"); + start(); + }); + + stop(); + }); + + + test("calling the completeCB with null assertion", function() { + relay.open(); + + channelMock.receiveGetVerifiedEmail(); + + relay.registerClient(function(origin, completeCB) { + completeCB(null, null); + strictEqual(channelMock.status, null, "channel gets the null assertion"); + start(); + }); + + stop(); + }); + + test("calling the completeCB with error", function() { + relay.open(); + + channelMock.receiveGetVerifiedEmail(); + + relay.registerClient(function(origin, completeCB) { + completeCB(null, "canceled"); + + equal(channelMock.errorCode, "canceled", "error callback called with error code"); + ok(channelMock.verboseError, "verbose error code set"); + + start(); + }); + + stop(); + }); +}); diff --git a/resources/static/dialog/test/qunit/resources/browser-support_unit_test.js b/resources/static/dialog/test/qunit/resources/browser-support_unit_test.js new file mode 100644 index 0000000000000000000000000000000000000000..a26857f01eef422ad03c0cc49fbb4d9257957cef --- /dev/null +++ b/resources/static/dialog/test/qunit/resources/browser-support_unit_test.js @@ -0,0 +1,102 @@ +/*jshint browsers:true, forin: true, laxbreak: true */ +/*global steal: true, test: true, start: true, stop: true, module: true, ok: true, equal: true, BrowserID: true */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla BrowserID. + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +steal.plugins("jquery", "funcunit/qunit").then(function() { + "use strict"; + + var bid = BrowserID, + support = bid.BrowserSupport, + stubWindow, + stubNavigator; + + module("browser-support", { + setup: function() { + // Hard coded goodness for testing purposes + stubNavigator = { + appName: "Netscape", + userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:7.0.1) Gecko/20100101 Firefox/7.0.1" + }; + + stubWindow = { + localStorage: {}, + postMessage: function() {} + }; + + support.setTestEnv(stubNavigator, stubWindow); + }, + + teardown: function() { + } + }); + + test("browser without localStorage", function() { + delete stubWindow.localStorage; + + equal(support.isSupported(), false, "window.localStorage is required"); + equal(support.getNoSupportReason(), "LOCALSTORAGE", "correct reason"); + }); + + + test("browser without postMessage", function() { + delete stubWindow.postMessage; + + equal(support.isSupported(), false, "window.postMessage is required"); + equal(support.getNoSupportReason(), "POSTMESSAGE", "correct reason"); + }); + + test("Fake being IE8 - unsupported intentionally", function() { + stubNavigator.appName = "Microsoft Internet Explorer"; + stubNavigator.userAgent = "MSIE 8.0"; + + equal(support.isSupported(), false, "IE8 is not supported"); + equal(support.getNoSupportReason(), "IE_VERSION", "correct reason"); + }); + + test("Fake being IE9 - supported", function() { + stubNavigator.appName = "Microsoft Internet Explorer"; + stubNavigator.userAgent = "MSIE 9.0"; + + equal(support.isSupported(), true, "IE9 is supported"); + equal(typeof support.getNoSupportReason(), "undefined", "no reason, we are all good"); + }); + + test("Firefox 7.01 with postMessage, localStorage", function() { + equal(support.isSupported(), true, "Firefox 7.01 is supported"); + equal(typeof support.getNoSupportReason(), "undefined", "no reason, we are all good"); + }); +}); + + diff --git a/resources/static/dialog/test/qunit/resources/channel_unit_test.js b/resources/static/dialog/test/qunit/resources/channel_unit_test.js new file mode 100644 index 0000000000000000000000000000000000000000..cd9621638a5b131270a3296f3455fd7b18fa7f1b --- /dev/null +++ b/resources/static/dialog/test/qunit/resources/channel_unit_test.js @@ -0,0 +1,162 @@ +/*jshint browsers:true, forin: true, laxbreak: true */ +/*global steal: true, test: true, start: true, stop: true, module: true, ok: true, equal: true, BrowserID:true */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla BrowserID. + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/channel", function() { + var channel = BrowserID.Channel; + + var navMock = { + id: {}, + }; + + // Mock in the window object as well as the frame relay + var winMock = { + location: { + href: "browserid.org/sign_in#1234" + }, + opener: { + frames: { + browserid_relay_1234: { + BrowserID: { + Relay: { + registerClient: function(callback) { + // mock of the registerClient function in the BrowserID.Channel. + callback("origin", function onComplete(success, error) { + winMock.success = success; + winMock.error = error; + }); + } + } + } + } + } + } + }; + + module("resources/channel", { + setup: function() { + }, + + teardown: function() { + channel.init({ + window: window, + navigator: navigator + }); + } + }); + + test("window.setupChannel exists for legacy uses", function() { + ok(typeof window.setupChannel, "function", "window.setupChannel exists for legacy uses"); + }); + + test("IFRAME channel with assertion", function() { + channel.init({ + window: winMock, + navigator: navMock + }); + + + channel.open({ + getVerifiedEmail: function(origin, onsuccess, onerror) { + onsuccess("assertion"); + equal(winMock.success, "assertion", "assertion made it to the relay"); + start(); + } + }); + + stop(); + }); + + test("IFRAME channel with null assertion", function() { + channel.init({ + window: winMock, + navigator: navMock + }); + + channel.open({ + getVerifiedEmail: function(origin, onsuccess, onerror) { + onsuccess(null); + strictEqual(winMock.success, null, "null assertion made it to the relay"); + start(); + } + }); + + stop(); + }); + + test("IFRAME channel relaying error", function() { + channel.init({ + window: winMock, + navigator: navMock + }); + + channel.open({ + getVerifiedEmail: function(origin, onsuccess, onerror) { + onerror("error"); + strictEqual(winMock.error, "error", "error made it to the relay"); + start(); + } + }); + + stop(); + }); + + test("IFRAME channel with error on open", function() { + var winMockWithoutRelay = $.extend(true, {}, winMock); + delete winMockWithoutRelay.opener.frames.browserid_relay_1234; + + channel.init({ + window: winMockWithoutRelay, + navigator: navMock + }); + + // Do this manually so we can test if getVerifiedEmail gets called. + try { + channel.open({ + getVerifiedEmail: function(origin, onsuccess, onerror) { + ok(false, "getVerifiedEmail should never be called on channel error"); + start(); + } + }); + } catch(e) { + equal(e.toString(), "relay frame not found", "exception caught when trying to open channel that does not exist"); + start(); + } + + stop(); + }); + +}); + diff --git a/browserid/static/dialog/test/qunit/resources/network_unit_test.js b/resources/static/dialog/test/qunit/resources/network_unit_test.js similarity index 77% rename from browserid/static/dialog/test/qunit/resources/network_unit_test.js rename to resources/static/dialog/test/qunit/resources/network_unit_test.js index ac5b06c785ffba87efe2ea7f3d2b3404aefb6b16..25b95e2fca5adb2dad7aed5a001dbda30d96779a 100644 --- a/browserid/static/dialog/test/qunit/resources/network_unit_test.js +++ b/resources/static/dialog/test/qunit/resources/network_unit_test.js @@ -34,10 +34,11 @@ * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ -steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", function() { +steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", "/dialog/test/qunit/mocks/xhr", function() { "use strict"; - var testName; + var testName, + xhr = BrowserID.Mocks.xhr; function wrappedAsyncTest(name, test) { asyncTest(name, function() { @@ -61,8 +62,12 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func var handle; - var subscriber = function() { + var subscriber = function(message, info) { ok(true, "xhr error notified application"); + ok(info.network.url, "url is in network info"); + ok(info.network.type, "request type is in network info"); + equal(info.network.textStatus, "errorStatus", "textStatus is in network info"); + equal(info.network.errorThrown, "errorThrown", "errorThrown is in response info"); wrappedStart(); OpenAjax.hub.unsubscribe(handle); }; @@ -85,8 +90,12 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func args.push(function onSuccess(authenticated) { ok(false, "XHR failure should never pass"); wrappedStart(); - }, function onFailure() { + }, function onFailure(info) { ok(true, "XHR failure should never pass"); + ok(info.network.url, "url is in network info"); + ok(info.network.type, "request type is in network info"); + equal(info.network.textStatus, "errorStatus", "textStatus is in network info"); + equal(info.network.errorThrown, "errorThrown", "errorThrown is in response info"); wrappedStart(); }); @@ -97,105 +106,9 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func stop(); } - var network = BrowserID.Network, - contextInfo = { - server_time: new Date().getTime(), - csrf_token: "csrf", - authenticated: false - }; - - - /** - * This is the results table, the keys are the request type, url, and - * a "selector" for testing. The right is the expected return value, already - * decoded. If a result is "undefined", the request's error handler will be - * called. - */ - var xhr = { - results: { - "get /wsapi/session_context valid": contextInfo, - "get /wsapi/session_context invalid": contextInfo, - // We are going to test for XHR failures for session_context using - // call to serverTime. We are going to use the flag contextAjaxError - "get /wsapi/session_context ajaxError": contextInfo, - "get /wsapi/session_context contextAjaxError": undefined, - "post /wsapi/authenticate_user valid": { success: true }, - "post /wsapi/authenticate_user invalid": { success: false }, - "post /wsapi/authenticate_user ajaxError": undefined, - "post /wsapi/complete_email_addition valid": { success: true }, - "post /wsapi/complete_email_addition invalid": { success: false }, - "post /wsapi/complete_email_addition ajaxError": undefined, - "post /wsapi/stage_user valid": { success: true }, - "post /wsapi/stage_user invalid": { success: false }, - "post /wsapi/stage_user ajaxError": undefined, - "get /wsapi/user_creation_status?email=address notcreated": undefined, // undefined because server returns 400 error - "get /wsapi/user_creation_status?email=address pending": { status: "pending" }, - "get /wsapi/user_creation_status?email=address complete": { status: "complete" }, - "get /wsapi/user_creation_status?email=address ajaxError": undefined, - "post /wsapi/complete_user_creation valid": { success: true }, - "post /wsapi/complete_user_creation invalid": { success: false }, - "post /wsapi/complete_user_creation ajaxError": undefined, - "post /wsapi/logout valid": { success: true }, - "post /wsapi/logout ajaxError": undefined, - "get /wsapi/have_email?email=address taken": { email_known: true }, - "get /wsapi/have_email?email=address nottaken" : { email_known: false }, - "get /wsapi/have_email?email=address ajaxError" : undefined, - "post /wsapi/remove_email valid": { success: true }, - "post /wsapi/remove_email invalid": { success: false }, - "post /wsapi/remove_email ajaxError": undefined, - "post /wsapi/account_cancel valid": { success: true }, - "post /wsapi/account_cancel invalid": { success: false }, - "post /wsapi/account_cancel ajaxError": undefined, - "post /wsapi/stage_email valid": { success: true }, - "post /wsapi/stage_email invalid": { success: false }, - "post /wsapi/stage_email ajaxError": undefined, - "get /wsapi/email_addition_status?email=address notcreated": undefined, // undefined because server returns 400 error - "get /wsapi/email_addition_status?email=address pending": { status: "pending" }, - "get /wsapi/email_addition_status?email=address complete": { status: "complete" }, - "get /wsapi/email_addition_status?email=address ajaxError": undefined - }, - - useResult: function(result) { - xhr.resultType = result; - }, - - getLastRequest: function() { - return this.req; - }, - - ajax: function(obj) { - //console.log("ajax request"); - var type = obj.type ? obj.type.toLowerCase() : "get"; - - var req = this.req = { - type: type, - url: obj.url, - data: obj.data - }; - - - if(type === "post" && !obj.data.csrf) { - ok(false, "missing csrf token on POST request"); - } - - var resName = req.type + " " + req.url + " " + xhr.resultType; - var result = xhr.results[resName]; - - if(result) { - if(obj.success) { - obj.success(result); - } - } - else if (obj.error) { - // Invalid result - either invalid URL, invalid GET/POST or - // invalid resultType - obj.error(); - } - } - } + var network = BrowserID.Network; - - module("network", { + module("/resources/network", { setup: function() { network.setXHR(xhr); xhr.useResult("valid"); @@ -241,7 +154,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func wrappedAsyncTest("checkAuth with valid authentication", function() { - contextInfo.authenticated = true; + xhr.setContextInfo("authenticated", true); network.checkAuth(function onSuccess(authenticated) { equal(authenticated, true, "we have an authentication"); wrappedStart(); @@ -255,7 +168,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func wrappedAsyncTest("checkAuth with invalid authentication", function() { xhr.useResult("invalid"); - contextInfo.authenticated = false; + xhr.setContextInfo("authenticated", false); network.checkAuth(function onSuccess(authenticated) { equal(authenticated, false, "we are not authenticated"); @@ -272,7 +185,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func wrappedAsyncTest("checkAuth with XHR failure", function() { xhr.useResult("ajaxError"); - contextInfo.authenticated = false; + xhr.setContextInfo("authenticated", false); // Do not convert this to failureCheck, we do this manually because // checkAuth does not make an XHR request. Since it does not make an XHR @@ -365,6 +278,20 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func stop(); }); + wrappedAsyncTest("createUser throttled", function() { + xhr.useResult("throttle"); + + network.createUser("validuser", "origin", function onSuccess(added) { + equal(added, false, "throttled email returns onSuccess but with false as the value"); + wrappedStart(); + }, function onFailure() { + ok(false); + wrappedStart(); + }); + + stop(); + }); + wrappedAsyncTest("createUser with XHR failure", function() { notificationCheck(network.createUser, "validuser", "origin"); }); @@ -376,7 +303,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func wrappedAsyncTest("checkUserRegistration with pending email", function() { xhr.useResult("pending"); - network.checkUserRegistration("address", function(status) { + network.checkUserRegistration("registered@testuser.com", function(status) { equal(status, "pending"); wrappedStart(); }, function onFailure() { @@ -390,7 +317,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func wrappedAsyncTest("checkUserRegistration with complete email", function() { xhr.useResult("complete"); - network.checkUserRegistration("address", function(status) { + network.checkUserRegistration("registered@testuser.com", function(status) { equal(status, "complete"); wrappedStart(); }, function onFailure() { @@ -402,11 +329,11 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func }); wrappedAsyncTest("checkUserRegistration with XHR failure", function() { - notificationCheck(network.checkUserRegistration, "address"); + notificationCheck(network.checkUserRegistration, "registered@testuser.com"); }); wrappedAsyncTest("checkUserRegistration with XHR failure", function() { - failureCheck(network.checkUserRegistration, "address"); + failureCheck(network.checkUserRegistration, "registered@testuser.com"); }); wrappedAsyncTest("completeUserRegistration with valid token", function() { @@ -479,9 +406,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func }); wrappedAsyncTest("emailRegistered with taken email", function() { - xhr.useResult("taken"); - - network.emailRegistered("address", function(taken) { + network.emailRegistered("registered@testuser.com", function(taken) { equal(taken, true, "a taken email is marked taken"); wrappedStart(); }, function onFailure() { @@ -493,9 +418,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func }); wrappedAsyncTest("emailRegistered with nottaken email", function() { - xhr.useResult("nottaken"); - - network.emailRegistered("address", function(taken) { + network.emailRegistered("unregistered@testuser.com", function(taken) { equal(taken, false, "a not taken email is not marked taken"); wrappedStart(); }, function onFailure() { @@ -507,11 +430,11 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func }); wrappedAsyncTest("emailRegistered with XHR failure", function() { - notificationCheck(network.emailRegistered, "address"); + notificationCheck(network.emailRegistered, "registered@testuser.com"); }); wrappedAsyncTest("emailRegistered with XHR failure", function() { - failureCheck(network.emailRegistered, "address"); + failureCheck(network.emailRegistered, "registered@testuser.com"); }); @@ -540,6 +463,20 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func stop(); }); + wrappedAsyncTest("addEmail throttled", function() { + xhr.useResult("throttle"); + + network.addEmail("address", "origin", function onSuccess(added) { + equal(added, false, "throttled email returns onSuccess but with false as the value"); + wrappedStart(); + }, function onFailure() { + ok(false); + wrappedStart(); + }); + + stop(); + }); + wrappedAsyncTest("addEmail with XHR failure", function() { notificationCheck(network.addEmail, "address", "origin"); }); @@ -551,7 +488,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func wrappedAsyncTest("checkEmailRegistration pending", function() { xhr.useResult("pending"); - network.checkEmailRegistration("address", function(status) { + network.checkEmailRegistration("registered@testuser.com", function(status) { equal(status, "pending"); wrappedStart(); }, function onFailure() { @@ -565,7 +502,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func wrappedAsyncTest("checkEmailRegistration complete", function() { xhr.useResult("complete"); - network.checkEmailRegistration("address", function(status) { + network.checkEmailRegistration("registered@testuser.com", function(status) { equal(status, "complete"); wrappedStart(); }, function onFailure() { @@ -706,7 +643,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func wrappedAsyncTest("serverTime", function() { // I am forcing the server time to be 1.25 seconds off. - contextInfo.server_time = new Date().getTime() - 1250; + xhr.setContextInfo("server_time", new Date().getTime() - 1250); network.serverTime(function onSuccess(time) { var diff = Math.abs((new Date()) - time); equal(1245 < diff && diff < 1255, true, "server time and local time should be less than 100ms different (is " + diff + "ms different)"); diff --git a/browserid/static/dialog/test/qunit/resources/storage_unit_test.js b/resources/static/dialog/test/qunit/resources/storage_unit_test.js similarity index 100% rename from browserid/static/dialog/test/qunit/resources/storage_unit_test.js rename to resources/static/dialog/test/qunit/resources/storage_unit_test.js diff --git a/browserid/run.js b/resources/static/dialog/test/qunit/resources/tooltip_unit_test.js old mode 100755 new mode 100644 similarity index 57% rename from browserid/run.js rename to resources/static/dialog/test/qunit/resources/tooltip_unit_test.js index 761fc95d6ef8c533bbe7c814bdb90aeb5935b537..85dae3a28f8e2ca87b09a4ebe4a1248cf49024ad --- a/browserid/run.js +++ b/resources/static/dialog/test/qunit/resources/tooltip_unit_test.js @@ -1,5 +1,5 @@ -#!/usr/bin/env node - +/*jshint browser:true, jQuery: true, forin: true, laxbreak:true */ +/*globals BrowserID: true, _:true */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * @@ -13,7 +13,7 @@ * for the specific language governing rights and limitations under the * License. * - * The Original Code is Mozilla BrowserID. + * The Original Code is Mozilla bid. * * The Initial Developer of the Original Code is Mozilla. * Portions created by the Initial Developer are Copyright (C) 2011 @@ -34,44 +34,47 @@ * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ +steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/tooltip", function() { + "use strict"; -var path = require("path"), -fs = require("fs"), -express = require("express"), -logger = require("../libs/logging.js").logger; + var bid = BrowserID, + tooltip = bid.Tooltip -const amMain = (process.argv[1] === __filename); + module("/resources/tooltip", { + setup: function() { + }, + teardown: function() { + } + }); -const PRIMARY_HOST = "127.0.0.1"; -const PRIMARY_PORT = 62700; -var handler = require("./app.js"); + test("show short tooltip, min of 2.5 seconds", function() { + var startTime = new Date().getTime(); -var app = undefined; + tooltip.showTooltip("#shortTooltip", function() { + console.log("calling tooltip back"); + var endTime = new Date().getTime(); + var diff = endTime - startTime; + ok(2000 <= diff && diff <= 3000, diff + " - minimum of 2 seconds, max of 3 seconds"); -exports.runServer = function() { - if (app) return; + start(); + }); - app = express.createServer(); + stop(); + }); - // let the specific server interact directly with the connect server to register their middleware - if (handler.setup) handler.setup(app); + test("show long tooltip, takes about 5 seconds", function() { + var startTime = new Date().getTime(); - // use the express 'static' middleware for serving of static files (cache headers, HTTP range, etc) - app.use(express.static(path.join(__dirname, "static"))); + tooltip.showTooltip("#longTooltip", function() { + var endTime = new Date().getTime(); + var diff = endTime - startTime; + ok(diff >= 4500, diff + " - longer tooltip is on the screen for a bit longer"); - app.listen(PRIMARY_PORT, PRIMARY_HOST); -}; + start(); + }); -exports.stopServer = function(cb) { - if (!app) return; - app.on('close', function() { - cb(); + stop(); }); - app.close(); - app = undefined; -} - -// when directly invoked from the command line, we'll start the server -if (amMain) exports.runServer(); +}); diff --git a/browserid/static/dialog/test/qunit/resources/user_unit_test.js b/resources/static/dialog/test/qunit/resources/user_unit_test.js similarity index 75% rename from browserid/static/dialog/test/qunit/resources/user_unit_test.js rename to resources/static/dialog/test/qunit/resources/user_unit_test.js index c03c3610bb1d6fd60571c04de664999005801071..387ea689d28d4e43806f2e30a354e8b65d39c021 100644 --- a/browserid/static/dialog/test/qunit/resources/user_unit_test.js +++ b/resources/static/dialog/test/qunit/resources/user_unit_test.js @@ -40,132 +40,16 @@ var jwcert = require("./jwcert"); steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", function() { var lib = BrowserID.User, - network = BrowserID.Network, storage = BrowserID.Storage, + xhr = BrowserID.Mocks.xhr, testOrigin = "testOrigin"; // I generated these locally, they are used nowhere else. var pubkey = {"algorithm":"RS","n":"56063028070432982322087418176876748072035482898334811368408525596198252519267108132604198004792849077868951906170812540713982954653810539949384712773390200791949565903439521424909576832418890819204354729217207360105906039023299561374098942789996780102073071760852841068989860403431737480182725853899733706069","e":"65537"}; - var privkey = {"algorithm":"RS","n":"56063028070432982322087418176876748072035482898334811368408525596198252519267108132604198004792849077868951906170812540713982954653810539949384712773390200791949565903439521424909576832418890819204354729217207360105906039023299561374098942789996780102073071760852841068989860403431737480182725853899733706069","e":"65537","d":"786150156350274055174913976906933968265264030754683486390396799104417261473770120296370873955240982995278496143719986037141619777024457729427415826765728988003471373990098269492312035966334999128083733012526716409629032119935282516842904344253703738413658199885458117908331858717294515041118355034573371553"}; - // this cert is meaningless, but it has the right format var random_cert = "eyJhbGciOiJSUzEyOCJ9.eyJpc3MiOiJpc3N1ZXIuY29tIiwiZXhwIjoxMzE2Njk1MzY3NzA3LCJwdWJsaWMta2V5Ijp7ImFsZ29yaXRobSI6IlJTIiwibiI6IjU2MDYzMDI4MDcwNDMyOTgyMzIyMDg3NDE4MTc2ODc2NzQ4MDcyMDM1NDgyODk4MzM0ODExMzY4NDA4NTI1NTk2MTk4MjUyNTE5MjY3MTA4MTMyNjA0MTk4MDA0NzkyODQ5MDc3ODY4OTUxOTA2MTcwODEyNTQwNzEzOTgyOTU0NjUzODEwNTM5OTQ5Mzg0NzEyNzczMzkwMjAwNzkxOTQ5NTY1OTAzNDM5NTIxNDI0OTA5NTc2ODMyNDE4ODkwODE5MjA0MzU0NzI5MjE3MjA3MzYwMTA1OTA2MDM5MDIzMjk5NTYxMzc0MDk4OTQyNzg5OTk2NzgwMTAyMDczMDcxNzYwODUyODQxMDY4OTg5ODYwNDAzNDMxNzM3NDgwMTgyNzI1ODUzODk5NzMzNzA2MDY5IiwiZSI6IjY1NTM3In0sInByaW5jaXBhbCI6eyJlbWFpbCI6InRlc3R1c2VyQHRlc3R1c2VyLmNvbSJ9fQ.aVIO470S_DkcaddQgFUXciGwq2F_MTdYOJtVnEYShni7I6mqBwK3fkdWShPEgLFWUSlVUtcy61FkDnq2G-6ikSx1fUZY7iBeSCOKYlh6Kj9v43JX-uhctRSB2pI17g09EUtvmb845EHUJuoowdBLmLa4DSTdZE-h4xUQ9MsY7Ik"; - var credentialsValid, unknownEmails, keyRefresh, syncValid, userEmails, - userCheckCount = 0, - emailCheckCount = 0, - registrationResponse, - xhrFailure = false, - validToken = true; - - var netStub = { - reset: function() { - credentialsValid = syncValid = true; - unknownEmails = []; - keyRefresh = []; - userEmails = {"testuser@testuser.com": {}}; - registrationResponse = "complete"; - xhrFailure = false; - }, - - checkUserRegistration: function(email, onSuccess, onFailure) { - userCheckCount++; - var status = userCheckCount === 2 ? registrationResponse : "pending"; - - xhrFailure ? onFailure() : onSuccess(status); - }, - - authenticate: function(email, password, onSuccess, onFailure) { - xhrFailure ? onFailure() : onSuccess(credentialsValid); - }, - - checkAuth: function(onSuccess, onFailure) { - xhrFailure ? onFailure() : onSuccess(credentialsValid); - }, - - emailRegistered: function(email, onSuccess, onFailure) { - xhrFailure ? onFailure() : onSuccess(email === "registered"); - }, - - addEmail: function(email, origin, onSuccess, onFailure) { - xhrFailure ? onFailure() : onSuccess(true); - }, - - checkEmailRegistration: function(email, onSuccess, onFailure) { - emailCheckCount++; - var status = emailCheckCount === 2 ? registrationResponse : "pending"; - - xhrFailure ? onFailure() : onSuccess(status); - }, - - emailForVerificationToken: function(token, onSuccess, onFailure) { - xhrFailure ? onFailure() : onSuccess("testuser@testuser.com"); - }, - - completeEmailRegistration: function(token, onSuccess, onFailure) { - xhrFailure ? onFailure() : onSuccess(validToken); - }, - - removeEmail: function(email, onSuccess, onFailure) { - xhrFailure ? onFailure() : onSuccess(); - }, - - listEmails: function(onSuccess, onFailure) { - xhrFailure ? onFailure() : onSuccess(userEmails); - }, - - certKey: function(email, pubkey, onSuccess, onFailure) { - if (syncValid) { - xhrFailure ? onFailure() : onSuccess(random_cert); - } - else { - onFailure(); - } - }, - - syncEmails: function(issued_identities, onSuccess, onFailure) { - xhrFailure ? onFailure() : onSuccess({ - unknown_emails: unknownEmails, - key_refresh: keyRefresh - }); - }, - - setKey: function(email, keypair, onSuccess, onFailure) { - if (syncValid) { - xhrFailure ? onFailure() : onSuccess(); - } - else { - onFailure(); - } - }, - - createUser: function(email, origin, onSuccess, onFailure) { - xhrFailure ? onFailure() : onSuccess(true); - }, - - setPassword: function(password, onSuccess) { - xhrFailure ? onFailure() : onSuccess(); - }, - - requestPasswordReset: function(email, origin, onSuccess, onFailure) { - xhrFailure ? onFailure() : onSuccess(true); - }, - - cancelUser: function(onSuccess, onFailure) { - xhrFailure ? onFailure() : onSuccess(); - }, - - serverTime: function(onSuccess, onFailure) { - xhrFailure ? onFailure() : onSuccess(new Date()); - }, - - logout: function(onSuccess, onFailure) { - credentialsValid = false; - xhrFailure ? onFailure() : onSuccess(); - } - }; - function testAssertion(assertion) { equal(typeof assertion, "string", "An assertion was correctly generated"); @@ -202,17 +86,14 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio */ } - module("user", { + module("resources/user", { setup: function() { - lib.setNetwork(netStub); + BrowserID.Network.setXHR(xhr); + xhr.useResult("valid"); lib.clearStoredEmailKeypairs(); - netStub.reset(); - userCheckCount = 0; - emailCheckCount = 0; - validToken = true; }, teardown: function() { - lib.setNetwork(BrowserID.Network); + BrowserID.Network.setXHR($); } }); @@ -280,8 +161,19 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio stop(); }); + test("createUser with user creation refused", function() { + xhr.useResult("throttle"); + + lib.createUser("testuser@testuser.com", function(status) { + equal(status, false, "user creation refused"); + start(); + }, failure("createUser failure")); + + stop(); + }); + test("createUser with XHR failure", function() { - xhrFailure = true; + xhr.useResult("ajaxError"); lib.createUser("testuser@testuser.com", function(status) { ok(false, "xhr failure should never succeed"); @@ -294,18 +186,15 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio stop(); }); - /** - * The next three tests use the mock network harness. The tests are testing - * the polling action and whether `waitForUserValidation` reacts as expected - * to the various network responses. The network harness simulates multiple - * calls to `checkUserRegistration`, attempting to simulate real use - * interaction to verify the email address, the first call to - * `checkUserRegistration` returns `pending`, the second returns the value - * stored in `registrationResponse`. - */ test("waitForUserValidation with `complete` response", function() { - lib.waitForUserValidation("testuser@testuser.com", function(status) { + storage.setStagedOnBehalfOf(testOrigin); + + xhr.useResult("complete"); + + lib.waitForUserValidation("registered@testuser.com", function(status) { equal(status, "complete", "complete response expected"); + + ok(!storage.getStagedOnBehalfOf(), "staged on behalf of is cleared when validation completes"); start(); }, failure("waitForUserValidation failure")); @@ -313,10 +202,14 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio }); test("waitForUserValidation with `mustAuth` response", function() { - registrationResponse = "mustAuth"; + storage.setStagedOnBehalfOf(testOrigin); + + xhr.useResult("mustAuth"); - lib.waitForUserValidation("testuser@testuser.com", function(status) { + lib.waitForUserValidation("registered@testuser.com", function(status) { equal(status, "mustAuth", "mustAuth response expected"); + + ok(!storage.getStagedOnBehalfOf(), "staged on behalf of is cleared when validation completes"); start(); }, failure("waitForUserValidation failure")); @@ -324,12 +217,15 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio }); test("waitForUserValidation with `noRegistration` response", function() { - registrationResponse = "noRegistration"; + xhr.useResult("noRegistration"); - lib.waitForUserValidation("baduser@testuser.com", function(status) { + storage.setStagedOnBehalfOf(testOrigin); + lib.waitForUserValidation("registered@testuser.com", function(status) { ok(false, "not expecting success") + start(); }, function(status) { + ok(storage.getStagedOnBehalfOf(), "staged on behalf of is cleared when validation completes"); ok(status, "noRegistration", "noRegistration response causes failure"); start(); }); @@ -338,12 +234,14 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio }); test("waitForUserValidation with XHR failure", function() { - xhrFailure = true; + xhr.useResult("ajaxError"); - lib.waitForUserValidation("baduser@testuser.com", function(status) { + storage.setStagedOnBehalfOf(testOrigin); + lib.waitForUserValidation("registered@testuser.com", function(status) { ok(false, "xhr failure should never succeed"); start(); }, function() { + ok(storage.getStagedOnBehalfOf(), "staged on behalf of is not cleared on XHR failure"); ok(true, "xhr failure should always be a failure"); start(); }); @@ -351,7 +249,51 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio stop(); }); + test("verifyUser with a good token", function() { + storage.setStagedOnBehalfOf(testOrigin); + + lib.verifyUser("token", "password", function onSuccess(info) { + + ok(info.valid, "token was valid"); + equal(info.email, "testuser@testuser.com", "email part of info"); + equal(info.origin, testOrigin, "origin in info"); + equal(storage.getStagedOnBehalfOf(), "", "initiating origin was removed"); + + start(); + }, failure("verifyUser failure")); + + stop(); + }); + + test("verifyUser with a bad token", function() { + xhr.useResult("invalid"); + lib.verifyUser("token", "password", function onSuccess(info) { + + equal(info.valid, false, "bad token calls onSuccess with a false validity"); + + start(); + }, failure("verifyUser failure")); + + stop(); + + }); + + test("verifyUser with an XHR failure", function() { + xhr.useResult("ajaxError"); + + lib.verifyUser("token", "password", function onSuccess(info) { + ok(false, "xhr failure should never succeed"); + start(); + }, function() { + ok(true, "xhr failure should always be a failure"); + start(); + }); + + stop(); + }); + + /* test("setPassword", function() { lib.setPassword("password", function() { // XXX fill this in. @@ -361,11 +303,53 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio stop(); }); +*/ + test("requestPasswordReset with known email", function() { + lib.requestPasswordReset("registered@testuser.com", function(status) { + equal(status.success, true, "password reset for known user"); + start(); + }, function() { + ok(false, "onFailure should not be called"); + start(); + }); + + stop(); + }); - test("requestPasswordReset", function() { - lib.requestPasswordReset("address", function(reset) { - // XXX fill this in. - ok(true); + test("requestPasswordReset with unknown email", function() { + lib.requestPasswordReset("unregistered@testuser.com", function(status) { + equal(status.success, false, "password not reset for unknown user"); + equal(status.reason, "invalid_user", "invalid_user is the reason"); + start(); + }, function() { + ok(false, "onFailure should not be called"); + start(); + }); + + stop(); + }); + + test("requestPasswordReset with throttle", function() { + xhr.useResult("throttle"); + lib.requestPasswordReset("registered@testuser.com", function(status) { + equal(status.success, false, "password not reset for throttle"); + equal(status.reason, "throttle", "password reset was throttled"); + start(); + }, function() { + ok(false, "onFailure should not be called"); + start(); + }); + + stop(); + }); + + test("requestPasswordReset with XHR failure", function() { + xhr.useResult("ajaxError"); + lib.requestPasswordReset("registered@testuser.com", function(status) { + ok(false, "xhr failure should never succeed"); + start(); + }, function() { + ok(true, "xhr failure should always be a failure"); start(); }); @@ -385,7 +369,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio test("authenticate with invalid credentials", function() { - credentialsValid = false; + xhr.useResult("invalid"); lib.authenticate("testuser@testuser.com", "testuser", function onComplete(authenticated) { equal(false, authenticated, "invalid authentication."); start(); @@ -397,7 +381,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio test("authenticate with XHR failure", function() { - xhrFailure = true; + xhr.useResult("ajaxError"); lib.authenticate("testuser@testuser.com", "testuser", function onComplete(authenticated) { ok(false, "xhr failure should never succeed"); start(); @@ -412,7 +396,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio test("checkAuthentication with valid authentication", function() { - credentialsValid = true; + xhr.setContextInfo("authenticated", true); lib.checkAuthentication(function(authenticated) { equal(authenticated, true, "We are authenticated!"); start(); @@ -424,7 +408,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio test("checkAuthentication with invalid authentication", function() { - credentialsValid = false; + xhr.setContextInfo("authenticated", false); lib.checkAuthentication(function(authenticated) { equal(authenticated, false, "We are not authenticated!"); start(); @@ -436,7 +420,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio test("checkAuthentication with XHR failure", function() { - xhrFailure = true; + xhr.useResult("contextAjaxError"); lib.checkAuthentication(function(authenticated) { ok(false, "xhr failure should never succeed"); start(); @@ -451,7 +435,8 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio test("checkAuthenticationAndSync with valid authentication", function() { - credentialsValid = true; + xhr.setContextInfo("authenticated", true); + lib.checkAuthenticationAndSync(function onSuccess() {}, function onComplete(authenticated) { equal(authenticated, true, "We are authenticated!"); @@ -464,7 +449,8 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio test("checkAuthenticationAndSync with invalid authentication", function() { - credentialsValid = false; + xhr.setContextInfo("authenticated", false); + lib.checkAuthenticationAndSync(function onSuccess() { ok(false, "We are not authenticated!"); start(); @@ -478,9 +464,10 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio test("checkAuthenticationAndSync with XHR failure", function() { - xhrFailure = true; + xhr.setContextInfo("authenticated", true); + xhr.useResult("ajaxError"); + lib.checkAuthenticationAndSync(function onSuccess() { - ok(false, "xhr failure should never succeed"); }, function onComplete() { ok(false, "xhr failure should never succeed"); @@ -495,7 +482,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio test("isEmailRegistered with registered email", function() { - lib.isEmailRegistered("registered", function(registered) { + lib.isEmailRegistered("registered@testuser.com", function(registered) { ok(registered); start(); }, function onFailure() { @@ -506,8 +493,8 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio stop(); }); - test("isEmailRegistered with non-registered email", function() { - lib.isEmailRegistered("nonregistered", function(registered) { + test("isEmailRegistered with unregistered email", function() { + lib.isEmailRegistered("unregistered@testuser.com", function(registered) { equal(registered, false); start(); }, function onFailure() { @@ -519,7 +506,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio }); test("isEmailRegistered with XHR failure", function() { - xhrFailure = true; + xhr.useResult("ajaxError"); lib.isEmailRegistered("registered", function(registered) { ok(false, "xhr failure should never succeed"); start(); @@ -538,7 +525,6 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio var identities = lib.getStoredEmailKeypairs(); equal(false, "testemail@testemail.com" in identities, "Our new email is not added until confirmation."); - equal(storage.getStagedOnBehalfOf(), lib.getHostname(), "initiatingOrigin is stored"); start(); @@ -547,8 +533,25 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio stop(); }); + test("addEmail with addition refused", function() { + xhr.useResult("throttle"); + + lib.addEmail("testemail@testemail.com", function(added) { + equal(added, false, "user addition was refused"); + + var identities = lib.getStoredEmailKeypairs(); + equal(false, "testemail@testemail.com" in identities, "Our new email is not added until confirmation."); + + equal(typeof storage.getStagedOnBehalfOf(), "undefined", "initiatingOrigin is not stored"); + + start(); + }, failure("addEmail failure")); + + stop(); + }); + test("addEmail with XHR failure", function() { - xhrFailure = true; + xhr.useResult("ajaxError"); lib.addEmail("testemail@testemail.com", function(added) { ok(false, "xhr failure should never succeed"); start(); @@ -561,18 +564,12 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio }); - - /** - * The next three tests use the mock network harness. The tests are testing - * the polling action and whether `waitForEmailValidation` reacts as expected - * to the various network responses. The network harness simulates multiple - * calls to `checkEmailRegistration`, attempting to simulate real use - * interaction to verify the email address, the first call to - * `checkEmailRegistration` returns `pending`, the second returns the value - * stored in `registrationResponse`. - */ test("waitForEmailValidation `complete` response", function() { - lib.waitForEmailValidation("testemail@testemail.com", function(status) { + storage.setStagedOnBehalfOf(testOrigin); + + xhr.useResult("complete"); + lib.waitForEmailValidation("registered@testuser.com", function(status) { + ok(!storage.getStagedOnBehalfOf(), "staged on behalf of is cleared when validation completes"); equal(status, "complete", "complete response expected"); start(); }, failure("waitForEmailValidation failure")); @@ -581,9 +578,11 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio }); test("waitForEmailValidation `mustAuth` response", function() { - registrationResponse = "mustAuth"; + storage.setStagedOnBehalfOf(testOrigin); + xhr.useResult("mustAuth"); - lib.waitForEmailValidation("testemail@testemail.com", function(status) { + lib.waitForEmailValidation("registered@testuser.com", function(status) { + ok(!storage.getStagedOnBehalfOf(), "staged on behalf of is cleared when validation completes"); equal(status, "mustAuth", "mustAuth response expected"); start(); }, failure("waitForEmailValidation failure")); @@ -592,12 +591,14 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio }); test("waitForEmailValidation with `noRegistration` response", function() { - registrationResponse = "noRegistration"; + storage.setStagedOnBehalfOf(testOrigin); + xhr.useResult("noRegistration"); - lib.waitForEmailValidation("baduser@testuser.com", function(status) { + lib.waitForEmailValidation("registered@testuser.com", function(status) { ok(false, "not expecting success") start(); }, function(status) { + ok(storage.getStagedOnBehalfOf(), "staged on behalf of is cleared when validation completes"); ok(status, "noRegistration", "noRegistration response causes failure"); start(); }); @@ -607,11 +608,14 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio test("waitForEmailValidation XHR failure", function() { - xhrFailure = true; - lib.waitForEmailValidation("testemail@testemail.com", function(status) { + storage.setStagedOnBehalfOf(testOrigin); + xhr.useResult("ajaxError"); + + lib.waitForEmailValidation("registered@testuser.com", function(status) { ok(false, "xhr failure should never succeed"); start(); }, function() { + ok(storage.getStagedOnBehalfOf(), "staged on behalf of is cleared when validation completes"); ok(true, "xhr failure should always be a failure"); start(); }); @@ -636,7 +640,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio }); test("verifyEmail with a bad token", function() { - validToken = false; + xhr.useResult("invalid"); lib.verifyEmail("token", function onSuccess(info) { @@ -650,7 +654,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio }); test("verifyEmail with an XHR failure", function() { - xhrFailure = true; + xhr.useResult("ajaxError"); lib.verifyEmail("token", function onSuccess(info) { ok(false, "xhr failure should never succeed"); @@ -664,7 +668,6 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio }); test("syncEmailKeypair with successful sync", function() { - syncValid = true; lib.syncEmailKeypair("testemail@testemail.com", function(keypair) { var identity = lib.getStoredEmailKeypair("testemail@testemail.com"); @@ -680,7 +683,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio test("syncEmailKeypair with invalid sync", function() { - syncValid = false; + xhr.useResult("invalid"); lib.syncEmailKeypair("testemail@testemail.com", function(keypair) { ok(false, "sync was invalid, this should have failed"); start(); @@ -695,7 +698,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio }); test("syncEmailKeypair with XHR failure", function() { - xhrFailure = true; + xhr.useResult("ajaxError"); lib.syncEmailKeypair("testemail@testemail.com", function(keypair) { ok(false, "xhr failure should never succeed"); start(); @@ -735,7 +738,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio test("removeEmail with XHR failure", function() { storage.addEmail("testemail@testemail.com", {pub: "pub", priv: "priv"}); - xhrFailure = true; + xhr.useResult("ajaxError"); lib.removeEmail("testemail@testemail.com", function() { ok(false, "xhr failure should never succeed"); start(); @@ -751,8 +754,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio test("syncEmails with no pre-loaded identities and no identities to add", function() { - userEmails = {}; - + xhr.useResult("no_identities"); lib.syncEmails(function onSuccess() { var identities = lib.getStoredEmailKeypairs(); ok(true, "we have synced identities"); @@ -764,8 +766,6 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio }); test("syncEmails with no pre-loaded identities and identities to add", function() { - userEmails = {"testuser@testuser.com": {}}; - lib.syncEmails(function onSuccess() { var identities = lib.getStoredEmailKeypairs(); ok("testuser@testuser.com" in identities, "Our new email is added"); @@ -777,7 +777,6 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio }); test("syncEmails with identities preloaded and none to add", function() { - userEmails = {"testuser@testuser.com": {}}; storage.addEmail("testuser@testuser.com", {}); lib.syncEmails(function onSuccess() { var identities = lib.getStoredEmailKeypairs(); @@ -792,8 +791,8 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio test("syncEmails with identities preloaded and one to add", function() { storage.addEmail("testuser@testuser.com", {pubkey: pubkey, cert: random_cert}); - userEmails = {"testuser@testuser.com": {pubkey: pubkey, cert: random_cert}, - "testuser2@testuser.com": {pubkey: pubkey, cert: random_cert}}; + + xhr.useResult("multiple"); lib.syncEmails(function onSuccess() { var identities = lib.getStoredEmailKeypairs(); @@ -810,7 +809,6 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio test("syncEmails with identities preloaded and one to remove", function() { storage.addEmail("testuser@testuser.com", {pub: pubkey, cert: random_cert}); storage.addEmail("testuser2@testuser.com", {pub: pubkey, cert: random_cert}); - userEmails = {"testuser@testuser.com": { pub: pubkey, cert: random_cert}}; lib.syncEmails(function onSuccess() { var identities = lib.getStoredEmailKeypairs(); @@ -825,7 +823,6 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio test("syncEmails with one to refresh", function() { storage.addEmail("testuser@testuser.com", {pub: pubkey, cert: random_cert}); - keyRefresh = ["testuser@testuser.com"]; lib.syncEmails(function onSuccess() { var identities = lib.getStoredEmailKeypairs(); @@ -837,7 +834,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio }); test("syncEmails with XHR failure", function() { - xhrFailure = true; + xhr.useResult("ajaxError"); lib.syncEmails(function onSuccess() { ok(false, "xhr failure should never succeed"); @@ -888,7 +885,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio test("getAssertion with XHR failure", function() { lib.setOrigin(testOrigin); - xhrFailure = true; + xhr.useResult("ajaxError"); lib.syncEmailKeypair("testuser@testuser.com", function() { ok(false, "xhr failure should never succeed"); @@ -903,9 +900,6 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio test("logoutUser", function(onSuccess) { - credentialsValid = true; - keyRefresh = ["testuser@testuser.com"]; - lib.authenticate("testuser@testuser.com", "testuser", function(authenticated) { lib.syncEmails(function() { var storedIdentities = storage.getEmails(); @@ -915,7 +909,6 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio storedIdentities = storage.getEmails(); equal(_.size(storedIdentities), 0, "All items have been removed on logout"); - equal(credentialsValid, false, "credentials were invalidated in logout"); start(); }, failure("logoutUser failure")); }, failure("syncEmails failure")); @@ -925,12 +918,9 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio }); test("logoutUser with XHR failure", function(onSuccess) { - credentialsValid = true; - keyRefresh = ["testuser@testuser.com"]; - lib.authenticate("testuser@testuser.com", "testuser", function(authenticated) { lib.syncEmails(function() { - xhrFailure = true; + xhr.useResult("ajaxError"); lib.logoutUser(function() { ok(false, "xhr failure should never succeed"); @@ -958,7 +948,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio }); test("cancelUser with XHR failure", function(onSuccess) { - xhrFailure = true; + xhr.useResult("ajaxError"); lib.cancelUser(function() { ok(false, "xhr failure should never succeed"); start(); diff --git a/browserid/static/dialog/test/qunit/resources/validation_unit_test.js b/resources/static/dialog/test/qunit/resources/validation_unit_test.js similarity index 76% rename from browserid/static/dialog/test/qunit/resources/validation_unit_test.js rename to resources/static/dialog/test/qunit/resources/validation_unit_test.js index 1366573ce557a706e38e0d79ae67a7ad66b5a4f6..37925f8174bb184647f14f600f12e729707fe60c 100644 --- a/browserid/static/dialog/test/qunit/resources/validation_unit_test.js +++ b/resources/static/dialog/test/qunit/resources/validation_unit_test.js @@ -46,7 +46,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/browserid", fu tooltipShown = true; } - module("validation", { + module("resources/validation", { setup: function() { origShowTooltip = bid.Tooltip.showTooltip; bid.Tooltip.showTooltip = showTooltip; @@ -98,10 +98,77 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/browserid", fu test("email with empty email", function() { var valid = validation.email(""); - equal(valid, valid, "missing email is missing"); + equal(valid, false, "missing email is missing"); equal(tooltipShown, true, "missing email shows no tooltip"); }); + test("email with Capital Letters in local side", function() { + var valid = validation.email("X@y.z"); + + equal(valid, true, "capital letters allowed in local side"); + equal(tooltipShown, false, "capital letters in local side causes no tooltip"); + }); + + test("email with Capital Letters in domain side", function() { + var valid = validation.email("x@Y.z"); + + equal(valid, false, "capital letters not allowed in domain side"); + equal(tooltipShown, true, "missing email shows no tooltip"); + }); + + + test("email with 64 characters in local side", function() { + var local = ""; + + for(var i = 0; i < 64; i++) { + local += "a"; + } + + var valid = validation.email(local + "@y.z"); + + equal(valid, true, "64 characters allowed in local side"); + equal(tooltipShown, false, "64 characters causes no error"); + }); + + test("email with more than 64 characters in local side", function() { + var local = ""; + + for(var i = 0; i <= 64; i++) { + local += "a"; + } + + var valid = validation.email(local + "@y.z"); + + equal(valid, false, "only 64 characters allowed in local side"); + equal(tooltipShown, true, "65 characters causes an error"); + }); + + test("email with 254 characters", function() { + var domain = ""; + + for(var i = 0; i < 248; i++) { + domain += "a"; + } + + var valid = validation.email("x@" + domain * ".com"); + + equal(valid, false, "254 characters allowed in total address"); + equal(tooltipShown, true, "254 characters causes no error"); + }); + + test("email with more than 254 characters", function() { + var domain = ""; + + for(var i = 0; i <= 248; i++) { + domain += "a"; + } + + var valid = validation.email("x@" + domain * ".com"); + + equal(valid, false, "only 254 characters allowed in total address"); + equal(tooltipShown, true, "> 254 characters causes an error"); + }); + test("email with invalid email", function() { var valid = validation.email("testuser@testuser"); diff --git a/resources/static/dialog/views/authenticate.ejs b/resources/static/dialog/views/authenticate.ejs new file mode 100644 index 0000000000000000000000000000000000000000..78c6b3f94291f023102f7d6fd92c6a421709ba2a --- /dev/null +++ b/resources/static/dialog/views/authenticate.ejs @@ -0,0 +1,60 @@ + <strong>Sign in using</strong> + <div class="form_section"> + <ul class="inputs"> + + <li> + <label for="email" class="serif">Email</label> + <input id="email" class="sans" type="email" autocapitalize="off" autocorrect="off" value="<%= email %>" maxlength="254"/> + + <div id="email_format" class="tooltip" for="email"> + This field must be an email address. + </div> + + <div id="email_required" class="tooltip" for="email"> + The email field is required. + </div> + + <div id="could_not_add" class="tooltip" for="email"> + We just sent an email to that address! If you really want to send another, wait a minute or two and try again. + </div> + </li> + + <li id="hint_section" class="start"> + <p>Enter your email address to sign in to <strong><%= sitename %></strong></p> + </li> + + <li id="create_text_section" class="newuser"> + <p><strong>Welcome to BrowserID!</strong></p> + <p>This email looks new, so let's get you set up.</p> + </li> + + <li id="password_section" class="returning"> + + <label for="password" class="half serif">Password</label> + <div class="half right"> + <a id="forgotPassword" href="#">forgot your password?</a> + </div> + <input id="password" class="sans" type="password" maxlength="80"> + + + <div id="password_required" class="tooltip" for="password"> + The password field is required. + </div> + + <div id="cannot_authenticate" class="tooltip" for="password"> + The account cannot be logged in with this username and password. + </div> + </li> + + </ul> + + <div class="submit cf"> + <button class="start">next</button> + <button class="newuser">Verify Email</button> + + <button class="returning">sign in</button> + + <button class="forgot">Reset Password</button> + <button id="cancel_forgot_password" class="forgot">Cancel</button> + </div> + </div> diff --git a/browserid/static/dialog/views/confirmemail.ejs b/resources/static/dialog/views/confirmemail.ejs similarity index 100% rename from browserid/static/dialog/views/confirmemail.ejs rename to resources/static/dialog/views/confirmemail.ejs diff --git a/resources/static/dialog/views/error.ejs b/resources/static/dialog/views/error.ejs new file mode 100644 index 0000000000000000000000000000000000000000..537d2471f5a80b3305428035e80b950569a20ba9 --- /dev/null +++ b/resources/static/dialog/views/error.ejs @@ -0,0 +1,42 @@ + + <h2>We are very sorry, there has been an error!</h2> + + <p> + To retry, you will have to close BrowserID and try again. More info can be found by opening the expanded info below. + </p> + + <a href="#" id="openMoreInfo">See more info</a> + + <ul id="moreInfo"> + <% if (typeof action !== "undefined") { %> + <li> + <strong id="action">Action: </strong><%= action.title %> + + <% if (action.message) { %> + <p> + <%= action.message %> + </p> + <% } %> + </li> + <% } %> + + <% if (typeof network !== "undefined") { %> + <li> + + <strong id="network">Network Info:</strong> <%= network.type %>: <%= network.url %> + + <p> + <strong>Response Code - </strong> <%= network.textStatus %> + </p> + + <% if (network.errorThrown) { %> + <p> + <strong>Error Type:</strong> <%= network.errorThrown %> + </p> + <% } %> + + </li> + + <% } %> + + </ul> diff --git a/resources/static/dialog/views/offline.ejs b/resources/static/dialog/views/offline.ejs new file mode 100644 index 0000000000000000000000000000000000000000..677951bef1fc6a0fb4cb93eab91ed11c48f7d452 --- /dev/null +++ b/resources/static/dialog/views/offline.ejs @@ -0,0 +1,8 @@ + + <h2 id="offline">You are offline!</h2> + + <p> + We are sorry, but we cannot communicate with BrowserID while you are offline. + </p> + + diff --git a/browserid/static/dialog/views/pickemail.ejs b/resources/static/dialog/views/pickemail.ejs similarity index 79% rename from browserid/static/dialog/views/pickemail.ejs rename to resources/static/dialog/views/pickemail.ejs index de6582dadd816f542618943c87fd77c27d96a14a..d0ca8939fd53453f73732b09a0ee541e52f7a39b 100644 --- a/browserid/static/dialog/views/pickemail.ejs +++ b/resources/static/dialog/views/pickemail.ejs @@ -4,8 +4,11 @@ <ul class="inputs"> <% _.each(identities, function(email_obj, email_address) { %> <li> - <label for="<%= email_address %>" class="serif"> - <input type="radio" name="email" id="<%= email_address %>" value="<%= email_address %>" /> + + <label for="<%= email_address %>" class="serif<% if (email_address === siteemail) { %> preselected<% } %>"> + <input type="radio" name="email" id="<%= email_address %>" value="<%= email_address %>" + <% if (email_address === siteemail) { %> checked="checked" <% } %> + /> <%= email_address %> </label> </li> @@ -26,7 +29,7 @@ <ul class="inputs"> <li> <label for="newEmail" class="serif">New email address</label> - <input id="newEmail" name="newEmail" type="email" class="sans" autocapitalize="off" autocorrect="off" /> + <input id="newEmail" name="newEmail" type="email" class="sans" autocapitalize="off" autocorrect="off" maxlength="254"/> <div id="email_format" class="tooltip" for="newEmail"> This field must be an email address. @@ -37,7 +40,7 @@ </div> <div id="could_not_add" class="tooltip" for="newEmail"> - This email address could not be added. + We just sent an email to that address! If you really want to send another, wait a minute or two and try again. </div> <div id="already_taken" class="tooltip" for="newEmail"> diff --git a/browserid/static/dialog/views/testBodyTemplate.ejs b/resources/static/dialog/views/testBodyTemplate.ejs similarity index 100% rename from browserid/static/dialog/views/testBodyTemplate.ejs rename to resources/static/dialog/views/testBodyTemplate.ejs diff --git a/browserid/static/dialog/views/wait.ejs b/resources/static/dialog/views/wait.ejs similarity index 100% rename from browserid/static/dialog/views/wait.ejs rename to resources/static/dialog/views/wait.ejs diff --git a/browserid/static/favicon.ico b/resources/static/favicon.ico similarity index 100% rename from browserid/static/favicon.ico rename to resources/static/favicon.ico diff --git a/browserid/static/funcunit/.gitignore b/resources/static/funcunit/.gitignore similarity index 100% rename from browserid/static/funcunit/.gitignore rename to resources/static/funcunit/.gitignore diff --git a/browserid/static/funcunit/.gitmodules b/resources/static/funcunit/.gitmodules similarity index 100% rename from browserid/static/funcunit/.gitmodules rename to resources/static/funcunit/.gitmodules diff --git a/browserid/static/funcunit/README b/resources/static/funcunit/README similarity index 100% rename from browserid/static/funcunit/README rename to resources/static/funcunit/README diff --git a/browserid/static/funcunit/autosuggest/auto_suggest.js b/resources/static/funcunit/autosuggest/auto_suggest.js similarity index 100% rename from browserid/static/funcunit/autosuggest/auto_suggest.js rename to resources/static/funcunit/autosuggest/auto_suggest.js diff --git a/browserid/static/funcunit/autosuggest/autosuggest.css b/resources/static/funcunit/autosuggest/autosuggest.css similarity index 100% rename from browserid/static/funcunit/autosuggest/autosuggest.css rename to resources/static/funcunit/autosuggest/autosuggest.css diff --git a/browserid/static/funcunit/autosuggest/autosuggest.html b/resources/static/funcunit/autosuggest/autosuggest.html similarity index 100% rename from browserid/static/funcunit/autosuggest/autosuggest.html rename to resources/static/funcunit/autosuggest/autosuggest.html diff --git a/browserid/static/funcunit/autosuggest/autosuggest.js b/resources/static/funcunit/autosuggest/autosuggest.js similarity index 100% rename from browserid/static/funcunit/autosuggest/autosuggest.js rename to resources/static/funcunit/autosuggest/autosuggest.js diff --git a/browserid/static/funcunit/autosuggest/autosuggest_test.js b/resources/static/funcunit/autosuggest/autosuggest_test.js similarity index 100% rename from browserid/static/funcunit/autosuggest/autosuggest_test.js rename to resources/static/funcunit/autosuggest/autosuggest_test.js diff --git a/browserid/static/funcunit/autosuggest/funcunit.html b/resources/static/funcunit/autosuggest/funcunit.html similarity index 100% rename from browserid/static/funcunit/autosuggest/funcunit.html rename to resources/static/funcunit/autosuggest/funcunit.html diff --git a/browserid/static/funcunit/build.js b/resources/static/funcunit/build.js similarity index 100% rename from browserid/static/funcunit/build.js rename to resources/static/funcunit/build.js diff --git a/browserid/static/funcunit/dependencies.json b/resources/static/funcunit/dependencies.json similarity index 100% rename from browserid/static/funcunit/dependencies.json rename to resources/static/funcunit/dependencies.json diff --git a/browserid/static/funcunit/docs.html b/resources/static/funcunit/docs.html similarity index 100% rename from browserid/static/funcunit/docs.html rename to resources/static/funcunit/docs.html diff --git a/browserid/static/funcunit/drivers/selenium.js b/resources/static/funcunit/drivers/selenium.js similarity index 100% rename from browserid/static/funcunit/drivers/selenium.js rename to resources/static/funcunit/drivers/selenium.js diff --git a/browserid/static/funcunit/drivers/standard.js b/resources/static/funcunit/drivers/standard.js similarity index 100% rename from browserid/static/funcunit/drivers/standard.js rename to resources/static/funcunit/drivers/standard.js diff --git a/browserid/static/funcunit/envjs b/resources/static/funcunit/envjs similarity index 100% rename from browserid/static/funcunit/envjs rename to resources/static/funcunit/envjs diff --git a/browserid/static/funcunit/envjs.bat b/resources/static/funcunit/envjs.bat similarity index 100% rename from browserid/static/funcunit/envjs.bat rename to resources/static/funcunit/envjs.bat diff --git a/browserid/static/funcunit/funcunit.html b/resources/static/funcunit/funcunit.html similarity index 100% rename from browserid/static/funcunit/funcunit.html rename to resources/static/funcunit/funcunit.html diff --git a/browserid/static/funcunit/funcunit.js b/resources/static/funcunit/funcunit.js similarity index 100% rename from browserid/static/funcunit/funcunit.js rename to resources/static/funcunit/funcunit.js diff --git a/browserid/static/funcunit/generate_docs.html b/resources/static/funcunit/generate_docs.html similarity index 100% rename from browserid/static/funcunit/generate_docs.html rename to resources/static/funcunit/generate_docs.html diff --git a/browserid/static/funcunit/index.html b/resources/static/funcunit/index.html similarity index 100% rename from browserid/static/funcunit/index.html rename to resources/static/funcunit/index.html diff --git a/browserid/static/funcunit/java/extensions/fakesteal.js b/resources/static/funcunit/java/extensions/fakesteal.js similarity index 100% rename from browserid/static/funcunit/java/extensions/fakesteal.js rename to resources/static/funcunit/java/extensions/fakesteal.js diff --git a/browserid/static/funcunit/java/extensions/wrapped.js b/resources/static/funcunit/java/extensions/wrapped.js similarity index 100% rename from browserid/static/funcunit/java/extensions/wrapped.js rename to resources/static/funcunit/java/extensions/wrapped.js diff --git a/browserid/static/funcunit/java/selenium-java-client-driver.jar b/resources/static/funcunit/java/selenium-java-client-driver.jar similarity index 100% rename from browserid/static/funcunit/java/selenium-java-client-driver.jar rename to resources/static/funcunit/java/selenium-java-client-driver.jar diff --git a/browserid/static/funcunit/java/selenium-server-standalone-2.0b3.jar b/resources/static/funcunit/java/selenium-server-standalone-2.0b3.jar similarity index 100% rename from browserid/static/funcunit/java/selenium-server-standalone-2.0b3.jar rename to resources/static/funcunit/java/selenium-server-standalone-2.0b3.jar diff --git a/browserid/static/funcunit/java/user-extensions.js b/resources/static/funcunit/java/user-extensions.js similarity index 100% rename from browserid/static/funcunit/java/user-extensions.js rename to resources/static/funcunit/java/user-extensions.js diff --git a/browserid/static/funcunit/loader.js b/resources/static/funcunit/loader.js similarity index 100% rename from browserid/static/funcunit/loader.js rename to resources/static/funcunit/loader.js diff --git a/browserid/static/funcunit/pages/example.js b/resources/static/funcunit/pages/example.js similarity index 100% rename from browserid/static/funcunit/pages/example.js rename to resources/static/funcunit/pages/example.js diff --git a/browserid/static/funcunit/pages/follow.js b/resources/static/funcunit/pages/follow.js similarity index 100% rename from browserid/static/funcunit/pages/follow.js rename to resources/static/funcunit/pages/follow.js diff --git a/browserid/static/funcunit/pages/init.js b/resources/static/funcunit/pages/init.js similarity index 100% rename from browserid/static/funcunit/pages/init.js rename to resources/static/funcunit/pages/init.js diff --git a/browserid/static/funcunit/pages/mastering.js b/resources/static/funcunit/pages/mastering.js similarity index 100% rename from browserid/static/funcunit/pages/mastering.js rename to resources/static/funcunit/pages/mastering.js diff --git a/browserid/static/funcunit/pages/selenium.js b/resources/static/funcunit/pages/selenium.js similarity index 100% rename from browserid/static/funcunit/pages/selenium.js rename to resources/static/funcunit/pages/selenium.js diff --git a/browserid/static/funcunit/pages/setup.js b/resources/static/funcunit/pages/setup.js similarity index 100% rename from browserid/static/funcunit/pages/setup.js rename to resources/static/funcunit/pages/setup.js diff --git a/browserid/static/funcunit/pages/standalone.js b/resources/static/funcunit/pages/standalone.js similarity index 100% rename from browserid/static/funcunit/pages/standalone.js rename to resources/static/funcunit/pages/standalone.js diff --git a/browserid/static/funcunit/pages/writing.js b/resources/static/funcunit/pages/writing.js similarity index 100% rename from browserid/static/funcunit/pages/writing.js rename to resources/static/funcunit/pages/writing.js diff --git a/browserid/static/funcunit/qunit.html b/resources/static/funcunit/qunit.html similarity index 100% rename from browserid/static/funcunit/qunit.html rename to resources/static/funcunit/qunit.html diff --git a/browserid/static/funcunit/qunit/qunit.css b/resources/static/funcunit/qunit/qunit.css similarity index 100% rename from browserid/static/funcunit/qunit/qunit.css rename to resources/static/funcunit/qunit/qunit.css diff --git a/browserid/static/funcunit/qunit/qunit.js b/resources/static/funcunit/qunit/qunit.js similarity index 100% rename from browserid/static/funcunit/qunit/qunit.js rename to resources/static/funcunit/qunit/qunit.js diff --git a/browserid/static/funcunit/qunit/rhino/rhino.js b/resources/static/funcunit/qunit/rhino/rhino.js similarity index 100% rename from browserid/static/funcunit/qunit/rhino/rhino.js rename to resources/static/funcunit/qunit/rhino/rhino.js diff --git a/browserid/static/funcunit/qunit/test/qunit.html b/resources/static/funcunit/qunit/test/qunit.html similarity index 100% rename from browserid/static/funcunit/qunit/test/qunit.html rename to resources/static/funcunit/qunit/test/qunit.html diff --git a/browserid/static/funcunit/qunit/test/test.js b/resources/static/funcunit/qunit/test/test.js similarity index 100% rename from browserid/static/funcunit/qunit/test/test.js rename to resources/static/funcunit/qunit/test/test.js diff --git a/browserid/static/funcunit/resources/jquery.js b/resources/static/funcunit/resources/jquery.js similarity index 100% rename from browserid/static/funcunit/resources/jquery.js rename to resources/static/funcunit/resources/jquery.js diff --git a/browserid/static/funcunit/resources/json.js b/resources/static/funcunit/resources/json.js similarity index 100% rename from browserid/static/funcunit/resources/json.js rename to resources/static/funcunit/resources/json.js diff --git a/browserid/static/funcunit/resources/selector.js b/resources/static/funcunit/resources/selector.js similarity index 100% rename from browserid/static/funcunit/resources/selector.js rename to resources/static/funcunit/resources/selector.js diff --git a/browserid/static/funcunit/resources/selenium_start.js b/resources/static/funcunit/resources/selenium_start.js similarity index 100% rename from browserid/static/funcunit/resources/selenium_start.js rename to resources/static/funcunit/resources/selenium_start.js diff --git a/browserid/static/funcunit/scripts/run.js b/resources/static/funcunit/scripts/run.js similarity index 100% rename from browserid/static/funcunit/scripts/run.js rename to resources/static/funcunit/scripts/run.js diff --git a/browserid/static/funcunit/settings.js b/resources/static/funcunit/settings.js similarity index 100% rename from browserid/static/funcunit/settings.js rename to resources/static/funcunit/settings.js diff --git a/browserid/static/funcunit/summary.ejs b/resources/static/funcunit/summary.ejs similarity index 100% rename from browserid/static/funcunit/summary.ejs rename to resources/static/funcunit/summary.ejs diff --git a/browserid/static/funcunit/syn/.gitignore b/resources/static/funcunit/syn/.gitignore similarity index 100% rename from browserid/static/funcunit/syn/.gitignore rename to resources/static/funcunit/syn/.gitignore diff --git a/browserid/static/funcunit/syn/README b/resources/static/funcunit/syn/README similarity index 100% rename from browserid/static/funcunit/syn/README rename to resources/static/funcunit/syn/README diff --git a/browserid/static/funcunit/syn/browsers.js b/resources/static/funcunit/syn/browsers.js similarity index 100% rename from browserid/static/funcunit/syn/browsers.js rename to resources/static/funcunit/syn/browsers.js diff --git a/browserid/static/funcunit/syn/build.js b/resources/static/funcunit/syn/build.js similarity index 100% rename from browserid/static/funcunit/syn/build.js rename to resources/static/funcunit/syn/build.js diff --git a/browserid/static/funcunit/syn/demo.html b/resources/static/funcunit/syn/demo.html similarity index 100% rename from browserid/static/funcunit/syn/demo.html rename to resources/static/funcunit/syn/demo.html diff --git a/browserid/static/funcunit/syn/demo/record.js b/resources/static/funcunit/syn/demo/record.js similarity index 100% rename from browserid/static/funcunit/syn/demo/record.js rename to resources/static/funcunit/syn/demo/record.js diff --git a/browserid/static/funcunit/syn/drag/drag.html b/resources/static/funcunit/syn/drag/drag.html similarity index 100% rename from browserid/static/funcunit/syn/drag/drag.html rename to resources/static/funcunit/syn/drag/drag.html diff --git a/browserid/static/funcunit/syn/drag/drag.js b/resources/static/funcunit/syn/drag/drag.js similarity index 100% rename from browserid/static/funcunit/syn/drag/drag.js rename to resources/static/funcunit/syn/drag/drag.js diff --git a/browserid/static/funcunit/syn/drag/qunit.html b/resources/static/funcunit/syn/drag/qunit.html similarity index 100% rename from browserid/static/funcunit/syn/drag/qunit.html rename to resources/static/funcunit/syn/drag/qunit.html diff --git a/browserid/static/funcunit/syn/drag/test/qunit/drag_test.js b/resources/static/funcunit/syn/drag/test/qunit/drag_test.js similarity index 100% rename from browserid/static/funcunit/syn/drag/test/qunit/drag_test.js rename to resources/static/funcunit/syn/drag/test/qunit/drag_test.js diff --git a/browserid/static/funcunit/syn/drag/test/qunit/qunit.js b/resources/static/funcunit/syn/drag/test/qunit/qunit.js similarity index 100% rename from browserid/static/funcunit/syn/drag/test/qunit/qunit.js rename to resources/static/funcunit/syn/drag/test/qunit/qunit.js diff --git a/browserid/static/funcunit/syn/key.js b/resources/static/funcunit/syn/key.js similarity index 100% rename from browserid/static/funcunit/syn/key.js rename to resources/static/funcunit/syn/key.js diff --git a/browserid/static/funcunit/syn/mouse.js b/resources/static/funcunit/syn/mouse.js similarity index 100% rename from browserid/static/funcunit/syn/mouse.js rename to resources/static/funcunit/syn/mouse.js diff --git a/browserid/static/funcunit/syn/qunit.html b/resources/static/funcunit/syn/qunit.html similarity index 100% rename from browserid/static/funcunit/syn/qunit.html rename to resources/static/funcunit/syn/qunit.html diff --git a/browserid/static/funcunit/syn/recorder.html b/resources/static/funcunit/syn/recorder.html similarity index 100% rename from browserid/static/funcunit/syn/recorder.html rename to resources/static/funcunit/syn/recorder.html diff --git a/browserid/static/funcunit/syn/resources/jquery.event.drag.js b/resources/static/funcunit/syn/resources/jquery.event.drag.js similarity index 100% rename from browserid/static/funcunit/syn/resources/jquery.event.drag.js rename to resources/static/funcunit/syn/resources/jquery.event.drag.js diff --git a/browserid/static/funcunit/syn/resources/jquery.event.drop.js b/resources/static/funcunit/syn/resources/jquery.event.drop.js similarity index 100% rename from browserid/static/funcunit/syn/resources/jquery.event.drop.js rename to resources/static/funcunit/syn/resources/jquery.event.drop.js diff --git a/browserid/static/funcunit/syn/resources/jquery.js b/resources/static/funcunit/syn/resources/jquery.js similarity index 100% rename from browserid/static/funcunit/syn/resources/jquery.js rename to resources/static/funcunit/syn/resources/jquery.js diff --git a/browserid/static/funcunit/syn/resources/qunit/qunit.css b/resources/static/funcunit/syn/resources/qunit/qunit.css similarity index 100% rename from browserid/static/funcunit/syn/resources/qunit/qunit.css rename to resources/static/funcunit/syn/resources/qunit/qunit.css diff --git a/browserid/static/funcunit/syn/resources/qunit/qunit.js b/resources/static/funcunit/syn/resources/qunit/qunit.js similarity index 100% rename from browserid/static/funcunit/syn/resources/qunit/qunit.js rename to resources/static/funcunit/syn/resources/qunit/qunit.js diff --git a/browserid/static/funcunit/syn/syn.js b/resources/static/funcunit/syn/syn.js similarity index 100% rename from browserid/static/funcunit/syn/syn.js rename to resources/static/funcunit/syn/syn.js diff --git a/browserid/static/funcunit/syn/synthetic.html b/resources/static/funcunit/syn/synthetic.html similarity index 100% rename from browserid/static/funcunit/syn/synthetic.html rename to resources/static/funcunit/syn/synthetic.html diff --git a/browserid/static/funcunit/syn/synthetic.js b/resources/static/funcunit/syn/synthetic.js similarity index 100% rename from browserid/static/funcunit/syn/synthetic.js rename to resources/static/funcunit/syn/synthetic.js diff --git a/browserid/static/funcunit/syn/test/clickbasic.html b/resources/static/funcunit/syn/test/clickbasic.html similarity index 100% rename from browserid/static/funcunit/syn/test/clickbasic.html rename to resources/static/funcunit/syn/test/clickbasic.html diff --git a/browserid/static/funcunit/syn/test/qunit/h3.html b/resources/static/funcunit/syn/test/qunit/h3.html similarity index 100% rename from browserid/static/funcunit/syn/test/qunit/h3.html rename to resources/static/funcunit/syn/test/qunit/h3.html diff --git a/browserid/static/funcunit/syn/test/qunit/key_test.js b/resources/static/funcunit/syn/test/qunit/key_test.js similarity index 100% rename from browserid/static/funcunit/syn/test/qunit/key_test.js rename to resources/static/funcunit/syn/test/qunit/key_test.js diff --git a/browserid/static/funcunit/syn/test/qunit/mouse_test.js b/resources/static/funcunit/syn/test/qunit/mouse_test.js similarity index 100% rename from browserid/static/funcunit/syn/test/qunit/mouse_test.js rename to resources/static/funcunit/syn/test/qunit/mouse_test.js diff --git a/browserid/static/funcunit/syn/test/qunit/page1.html b/resources/static/funcunit/syn/test/qunit/page1.html similarity index 100% rename from browserid/static/funcunit/syn/test/qunit/page1.html rename to resources/static/funcunit/syn/test/qunit/page1.html diff --git a/browserid/static/funcunit/syn/test/qunit/page2.html b/resources/static/funcunit/syn/test/qunit/page2.html similarity index 100% rename from browserid/static/funcunit/syn/test/qunit/page2.html rename to resources/static/funcunit/syn/test/qunit/page2.html diff --git a/browserid/static/funcunit/syn/test/qunit/qunit.js b/resources/static/funcunit/syn/test/qunit/qunit.js similarity index 100% rename from browserid/static/funcunit/syn/test/qunit/qunit.js rename to resources/static/funcunit/syn/test/qunit/qunit.js diff --git a/browserid/static/funcunit/syn/test/qunit/syn_test.js b/resources/static/funcunit/syn/test/qunit/syn_test.js similarity index 100% rename from browserid/static/funcunit/syn/test/qunit/syn_test.js rename to resources/static/funcunit/syn/test/qunit/syn_test.js diff --git a/browserid/static/funcunit/syn/test/submit.html b/resources/static/funcunit/syn/test/submit.html similarity index 100% rename from browserid/static/funcunit/syn/test/submit.html rename to resources/static/funcunit/syn/test/submit.html diff --git a/browserid/static/funcunit/syn/test/submitted.html b/resources/static/funcunit/syn/test/submitted.html similarity index 100% rename from browserid/static/funcunit/syn/test/submitted.html rename to resources/static/funcunit/syn/test/submitted.html diff --git a/browserid/static/funcunit/template.html b/resources/static/funcunit/template.html similarity index 100% rename from browserid/static/funcunit/template.html rename to resources/static/funcunit/template.html diff --git a/browserid/static/funcunit/test/drag.html b/resources/static/funcunit/test/drag.html similarity index 100% rename from browserid/static/funcunit/test/drag.html rename to resources/static/funcunit/test/drag.html diff --git a/browserid/static/funcunit/test/findclosest.html b/resources/static/funcunit/test/findclosest.html similarity index 100% rename from browserid/static/funcunit/test/findclosest.html rename to resources/static/funcunit/test/findclosest.html diff --git a/browserid/static/funcunit/test/funcunit/find_closest_test.js b/resources/static/funcunit/test/funcunit/find_closest_test.js similarity index 100% rename from browserid/static/funcunit/test/funcunit/find_closest_test.js rename to resources/static/funcunit/test/funcunit/find_closest_test.js diff --git a/browserid/static/funcunit/test/funcunit/funcunit.js b/resources/static/funcunit/test/funcunit/funcunit.js similarity index 100% rename from browserid/static/funcunit/test/funcunit/funcunit.js rename to resources/static/funcunit/test/funcunit/funcunit.js diff --git a/browserid/static/funcunit/test/funcunit/funcunit_test.js b/resources/static/funcunit/test/funcunit/funcunit_test.js similarity index 100% rename from browserid/static/funcunit/test/funcunit/funcunit_test.js rename to resources/static/funcunit/test/funcunit/funcunit_test.js diff --git a/browserid/static/funcunit/test/funcunit/open_test.js b/resources/static/funcunit/test/funcunit/open_test.js similarity index 100% rename from browserid/static/funcunit/test/funcunit/open_test.js rename to resources/static/funcunit/test/funcunit/open_test.js diff --git a/browserid/static/funcunit/test/funcunit/protodrag_test.js b/resources/static/funcunit/test/funcunit/protodrag_test.js similarity index 100% rename from browserid/static/funcunit/test/funcunit/protodrag_test.js rename to resources/static/funcunit/test/funcunit/protodrag_test.js diff --git a/browserid/static/funcunit/test/funcunit/syn_test.js b/resources/static/funcunit/test/funcunit/syn_test.js similarity index 100% rename from browserid/static/funcunit/test/funcunit/syn_test.js rename to resources/static/funcunit/test/funcunit/syn_test.js diff --git a/browserid/static/funcunit/test/jquery.event.drag.js b/resources/static/funcunit/test/jquery.event.drag.js similarity index 100% rename from browserid/static/funcunit/test/jquery.event.drag.js rename to resources/static/funcunit/test/jquery.event.drag.js diff --git a/browserid/static/funcunit/test/jquery.event.drop.js b/resources/static/funcunit/test/jquery.event.drop.js similarity index 100% rename from browserid/static/funcunit/test/jquery.event.drop.js rename to resources/static/funcunit/test/jquery.event.drop.js diff --git a/browserid/static/funcunit/test/jquery.js b/resources/static/funcunit/test/jquery.js similarity index 100% rename from browserid/static/funcunit/test/jquery.js rename to resources/static/funcunit/test/jquery.js diff --git a/browserid/static/funcunit/test/myapp.html b/resources/static/funcunit/test/myapp.html similarity index 100% rename from browserid/static/funcunit/test/myapp.html rename to resources/static/funcunit/test/myapp.html diff --git a/browserid/static/funcunit/test/myotherapp.html b/resources/static/funcunit/test/myotherapp.html similarity index 100% rename from browserid/static/funcunit/test/myotherapp.html rename to resources/static/funcunit/test/myotherapp.html diff --git a/browserid/static/funcunit/test/protodrag/dragdrop.js b/resources/static/funcunit/test/protodrag/dragdrop.js similarity index 100% rename from browserid/static/funcunit/test/protodrag/dragdrop.js rename to resources/static/funcunit/test/protodrag/dragdrop.js diff --git a/browserid/static/funcunit/test/protodrag/effects.js b/resources/static/funcunit/test/protodrag/effects.js similarity index 100% rename from browserid/static/funcunit/test/protodrag/effects.js rename to resources/static/funcunit/test/protodrag/effects.js diff --git a/browserid/static/funcunit/test/protodrag/funcunit_test.js b/resources/static/funcunit/test/protodrag/funcunit_test.js similarity index 100% rename from browserid/static/funcunit/test/protodrag/funcunit_test.js rename to resources/static/funcunit/test/protodrag/funcunit_test.js diff --git a/browserid/static/funcunit/test/protodrag/myapp.html b/resources/static/funcunit/test/protodrag/myapp.html similarity index 100% rename from browserid/static/funcunit/test/protodrag/myapp.html rename to resources/static/funcunit/test/protodrag/myapp.html diff --git a/browserid/static/funcunit/test/protodrag/prototype.js b/resources/static/funcunit/test/protodrag/prototype.js similarity index 100% rename from browserid/static/funcunit/test/protodrag/prototype.js rename to resources/static/funcunit/test/protodrag/prototype.js diff --git a/browserid/static/funcunit/test/protodrag/scriptaculous.js b/resources/static/funcunit/test/protodrag/scriptaculous.js similarity index 100% rename from browserid/static/funcunit/test/protodrag/scriptaculous.js rename to resources/static/funcunit/test/protodrag/scriptaculous.js diff --git a/browserid/static/funcunit/test/qunit/qunit.js b/resources/static/funcunit/test/qunit/qunit.js similarity index 100% rename from browserid/static/funcunit/test/qunit/qunit.js rename to resources/static/funcunit/test/qunit/qunit.js diff --git a/browserid/static/funcunit/test/run.js b/resources/static/funcunit/test/run.js similarity index 100% rename from browserid/static/funcunit/test/run.js rename to resources/static/funcunit/test/run.js diff --git a/browserid/static/funcunit/update b/resources/static/funcunit/update similarity index 100% rename from browserid/static/funcunit/update rename to resources/static/funcunit/update diff --git a/browserid/static/i/a_better_way.png b/resources/static/i/a_better_way.png similarity index 100% rename from browserid/static/i/a_better_way.png rename to resources/static/i/a_better_way.png diff --git a/browserid/static/i/arrow.png b/resources/static/i/arrow.png similarity index 100% rename from browserid/static/i/arrow.png rename to resources/static/i/arrow.png diff --git a/browserid/static/i/bg.png b/resources/static/i/bg.png similarity index 100% rename from browserid/static/i/bg.png rename to resources/static/i/bg.png diff --git a/browserid/static/i/blink.gif b/resources/static/i/blink.gif similarity index 100% rename from browserid/static/i/blink.gif rename to resources/static/i/blink.gif diff --git a/browserid/static/i/browserid_logo_lil.png b/resources/static/i/browserid_logo_lil.png similarity index 100% rename from browserid/static/i/browserid_logo_lil.png rename to resources/static/i/browserid_logo_lil.png diff --git a/browserid/static/i/browserid_logo_sm.png b/resources/static/i/browserid_logo_sm.png similarity index 100% rename from browserid/static/i/browserid_logo_sm.png rename to resources/static/i/browserid_logo_sm.png diff --git a/browserid/static/i/card.png b/resources/static/i/card.png similarity index 100% rename from browserid/static/i/card.png rename to resources/static/i/card.png diff --git a/browserid/static/i/check.png b/resources/static/i/check.png similarity index 100% rename from browserid/static/i/check.png rename to resources/static/i/check.png diff --git a/browserid/static/i/count.png b/resources/static/i/count.png similarity index 100% rename from browserid/static/i/count.png rename to resources/static/i/count.png diff --git a/resources/static/i/firefox_logo.png b/resources/static/i/firefox_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c55a338081cc1e0f1d2e78fc50226695c0b37488 Binary files /dev/null and b/resources/static/i/firefox_logo.png differ diff --git a/browserid/static/i/hint.png b/resources/static/i/hint.png similarity index 100% rename from browserid/static/i/hint.png rename to resources/static/i/hint.png diff --git a/browserid/static/i/icon.png b/resources/static/i/icon.png similarity index 100% rename from browserid/static/i/icon.png rename to resources/static/i/icon.png diff --git a/browserid/static/i/labs-logo-small.png b/resources/static/i/labs-logo-small.png similarity index 100% rename from browserid/static/i/labs-logo-small.png rename to resources/static/i/labs-logo-small.png diff --git a/browserid/static/i/lock.png b/resources/static/i/lock.png similarity index 100% rename from browserid/static/i/lock.png rename to resources/static/i/lock.png diff --git a/browserid/static/i/sign_in_blue.png b/resources/static/i/sign_in_blue.png similarity index 100% rename from browserid/static/i/sign_in_blue.png rename to resources/static/i/sign_in_blue.png diff --git a/browserid/static/i/sign_in_green.png b/resources/static/i/sign_in_green.png similarity index 100% rename from browserid/static/i/sign_in_green.png rename to resources/static/i/sign_in_green.png diff --git a/browserid/static/i/sign_in_grey.png b/resources/static/i/sign_in_grey.png similarity index 100% rename from browserid/static/i/sign_in_grey.png rename to resources/static/i/sign_in_grey.png diff --git a/browserid/static/i/sign_in_orange.png b/resources/static/i/sign_in_orange.png similarity index 100% rename from browserid/static/i/sign_in_orange.png rename to resources/static/i/sign_in_orange.png diff --git a/browserid/static/i/sign_in_red.png b/resources/static/i/sign_in_red.png similarity index 100% rename from browserid/static/i/sign_in_red.png rename to resources/static/i/sign_in_red.png diff --git a/browserid/static/i/slit.png b/resources/static/i/slit.png similarity index 100% rename from browserid/static/i/slit.png rename to resources/static/i/slit.png diff --git a/browserid/static/i/sprite.png b/resources/static/i/sprite.png similarity index 100% rename from browserid/static/i/sprite.png rename to resources/static/i/sprite.png diff --git a/browserid/static/i/sunny.png b/resources/static/i/sunny.png similarity index 100% rename from browserid/static/i/sunny.png rename to resources/static/i/sunny.png diff --git a/browserid/static/i/times.gif b/resources/static/i/times.gif similarity index 100% rename from browserid/static/i/times.gif rename to resources/static/i/times.gif diff --git a/browserid/static/i/tutorial_1.png b/resources/static/i/tutorial_1.png similarity index 100% rename from browserid/static/i/tutorial_1.png rename to resources/static/i/tutorial_1.png diff --git a/browserid/static/i/tutorial_2.png b/resources/static/i/tutorial_2.png similarity index 100% rename from browserid/static/i/tutorial_2.png rename to resources/static/i/tutorial_2.png diff --git a/browserid/static/i/tutorial_3.png b/resources/static/i/tutorial_3.png similarity index 100% rename from browserid/static/i/tutorial_3.png rename to resources/static/i/tutorial_3.png diff --git a/browserid/static/include.js b/resources/static/include.js similarity index 93% rename from browserid/static/include.js rename to resources/static/include.js index 9f16d5d47007e557a9c7987df3a98f23471be0fb..cebeafc44cb225688d5ed9b36a062a33f9492e5f 100644 --- a/browserid/static/include.js +++ b/resources/static/include.js @@ -557,55 +557,88 @@ }; })(); - function getInternetExplorerVersion() { - var rv = -1; // Return value assumes failure. - if (navigator.appName == 'Microsoft Internet Explorer') { - var ua = navigator.userAgent; - var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})"); - if (re.exec(ua) != null) - rv = parseFloat(RegExp.$1); + var BrowserSupport = (function() { + var win = window, + nav = navigator, + reason; + + // For unit testing + function setTestEnv(newNav, newWindow) { + nav = newNav; + win = newWindow; } - return rv; - } - - function checkIE() { - var ieVersion = getInternetExplorerVersion(), - ieNosupport = ieVersion > -1 && ieVersion < 9, - message; + function getInternetExplorerVersion() { + var rv = -1; // Return value assumes failure. + if (nav.appName == 'Microsoft Internet Explorer') { + var ua = nav.userAgent; + var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})"); + if (re.exec(ua) != null) + rv = parseFloat(RegExp.$1); + } - if(ieNosupport) { - message = "Unfortunately, your version of Internet Explorer is not yet supported.\n" + - 'If you are using Internet Explorer 9, turn off "Compatibility View".'; + return rv; } - return message; - } + function checkIE() { + var ieVersion = getInternetExplorerVersion(), + ieNosupport = ieVersion > -1 && ieVersion < 9; - function explicitNosupport() { - var message = checkIE(); + if(ieNosupport) { + return "IE_VERSION"; + } + } - if (message) { - message += "\nWe are working hard to bring BrowserID support to your browser!"; - alert(message); + function explicitNosupport() { + return checkIE(); } - return message; - } + function checkLocalStorage() { + var localStorage = 'localStorage' in win && win['localStorage'] !== null; + if(!localStorage) { + return "LOCALSTORAGE"; + } + } - function checkRequirements() { - var localStorage = 'localStorage' in window && window['localStorage'] !== null; - var postMessage = !!window.postMessage; - var json = true; + function checkPostMessage() { + if(!win.postMessage) { + return "POSTMESSAGE"; + } + } - var explicitNo = explicitNosupport() + function isSupported() { + reason = checkLocalStorage() || checkPostMessage() || explicitNosupport(); - if(!explicitNo && !(localStorage && postMessage && json)) { - alert("Unfortunately, your browser does not meet the minimum HTML5 support required for BrowserID."); + return !reason; } - return localStorage && postMessage && json && !(explicitNo); - } + function getNoSupportReason() { + return reason; + } + + return { + /** + * Set the test environment. + * @method setTestEnv + */ + setTestEnv: setTestEnv, + /** + * Check whether the current browser is supported + * @method isSupported + * @returns {boolean} + */ + isSupported: isSupported, + /** + * Called after isSupported, if isSupported returns false. Gets the reason + * why browser is not supported. + * @method getNoSupportReason + * @returns {string} + */ + getNoSupportReason: getNoSupportReason + }; + + }()); + // this is for calls that are non-interactive function _open_hidden_iframe(doc) { @@ -636,16 +669,20 @@ return iframe; } - function _open_window() { + function _open_window(url) { + url = url || "about:blank"; // we open the window initially blank, and only after our relay frame has // been constructed do we update the location. This is done because we // must launch the window inside a click handler, but we should wait to // start loading it until our relay iframe is instantiated and ready. // see issue #287 & #286 - return window.open( - "about:blank", + var dialog = window.open( + url, "_mozid_signin", isFennec ? undefined : "menubar=0,location=0,resizable=0,scrollbars=0,status=0,dialog=1,width=700,height=375"); + + dialog.focus(); + return dialog; } function _attach_event(element, name, listener) { @@ -684,16 +721,17 @@ // keep track of these so that we can re-use/re-focus an already open window. navigator.id.getVerifiedEmail = function(callback) { - if(!checkRequirements()) { - return; - } - if (w) { // if there is already a window open, just focus the old window. w.focus(); return; } + if (!BrowserSupport.isSupported()) { + w = _open_window(ipServer + "/unsupported_dialog"); + return; + } + var frameid = _get_relayframe_id(); var iframe = _open_relayframe("browserid_relay_" + frameid); w = _open_window(); @@ -712,8 +750,7 @@ // has a problem re-attaching new iframes with the same name. Code inside // of frames with the same name sometimes does not get run. // See https://bugzilla.mozilla.org/show_bug.cgi?id=350023 - w.location = ipServer + "/sign_in#" + frameid; - w.focus(); + w = _open_window(ipServer + "/sign_in#" + frameid); } }); @@ -721,8 +758,10 @@ chan.destroy(); chan = null; - w.close(); - w = null; + if (w) { + w.close(); + w = null; + } iframe.parentNode.removeChild(iframe); iframe = null; diff --git a/browserid/static/jquery/.gitignore b/resources/static/jquery/.gitignore similarity index 100% rename from browserid/static/jquery/.gitignore rename to resources/static/jquery/.gitignore diff --git a/browserid/static/jquery/README b/resources/static/jquery/README similarity index 100% rename from browserid/static/jquery/README rename to resources/static/jquery/README diff --git a/browserid/static/jquery/build.js b/resources/static/jquery/build.js similarity index 100% rename from browserid/static/jquery/build.js rename to resources/static/jquery/build.js diff --git a/browserid/static/jquery/buildAll.js b/resources/static/jquery/buildAll.js similarity index 100% rename from browserid/static/jquery/buildAll.js rename to resources/static/jquery/buildAll.js diff --git a/browserid/static/jquery/class/class.html b/resources/static/jquery/class/class.html similarity index 100% rename from browserid/static/jquery/class/class.html rename to resources/static/jquery/class/class.html diff --git a/browserid/static/jquery/class/class.js b/resources/static/jquery/class/class.js similarity index 100% rename from browserid/static/jquery/class/class.js rename to resources/static/jquery/class/class.js diff --git a/browserid/static/jquery/class/class_test.js b/resources/static/jquery/class/class_test.js similarity index 100% rename from browserid/static/jquery/class/class_test.js rename to resources/static/jquery/class/class_test.js diff --git a/browserid/static/jquery/class/qunit.html b/resources/static/jquery/class/qunit.html similarity index 100% rename from browserid/static/jquery/class/qunit.html rename to resources/static/jquery/class/qunit.html diff --git a/browserid/static/jquery/controller/controller.html b/resources/static/jquery/controller/controller.html similarity index 100% rename from browserid/static/jquery/controller/controller.html rename to resources/static/jquery/controller/controller.html diff --git a/browserid/static/jquery/controller/controller.js b/resources/static/jquery/controller/controller.js similarity index 100% rename from browserid/static/jquery/controller/controller.js rename to resources/static/jquery/controller/controller.js diff --git a/browserid/static/jquery/controller/controller_test.js b/resources/static/jquery/controller/controller_test.js similarity index 100% rename from browserid/static/jquery/controller/controller_test.js rename to resources/static/jquery/controller/controller_test.js diff --git a/browserid/static/jquery/controller/history/history.html b/resources/static/jquery/controller/history/history.html similarity index 100% rename from browserid/static/jquery/controller/history/history.html rename to resources/static/jquery/controller/history/history.html diff --git a/browserid/static/jquery/controller/history/history.js b/resources/static/jquery/controller/history/history.js similarity index 100% rename from browserid/static/jquery/controller/history/history.js rename to resources/static/jquery/controller/history/history.js diff --git a/browserid/static/jquery/controller/history/html5/html5.js b/resources/static/jquery/controller/history/html5/html5.js similarity index 100% rename from browserid/static/jquery/controller/history/html5/html5.js rename to resources/static/jquery/controller/history/html5/html5.js diff --git a/browserid/static/jquery/controller/history/html5/qunit.html b/resources/static/jquery/controller/history/html5/qunit.html similarity index 100% rename from browserid/static/jquery/controller/history/html5/qunit.html rename to resources/static/jquery/controller/history/html5/qunit.html diff --git a/browserid/static/jquery/controller/history/html5/qunit/qunit.js b/resources/static/jquery/controller/history/html5/qunit/qunit.js similarity index 100% rename from browserid/static/jquery/controller/history/html5/qunit/qunit.js rename to resources/static/jquery/controller/history/html5/qunit/qunit.js diff --git a/browserid/static/jquery/controller/history/qunit.html b/resources/static/jquery/controller/history/qunit.html similarity index 100% rename from browserid/static/jquery/controller/history/qunit.html rename to resources/static/jquery/controller/history/qunit.html diff --git a/browserid/static/jquery/controller/history/qunit/qunit.js b/resources/static/jquery/controller/history/qunit/qunit.js similarity index 100% rename from browserid/static/jquery/controller/history/qunit/qunit.js rename to resources/static/jquery/controller/history/qunit/qunit.js diff --git a/browserid/static/jquery/controller/pages/document.js b/resources/static/jquery/controller/pages/document.js similarity index 100% rename from browserid/static/jquery/controller/pages/document.js rename to resources/static/jquery/controller/pages/document.js diff --git a/browserid/static/jquery/controller/pages/listening.js b/resources/static/jquery/controller/pages/listening.js similarity index 100% rename from browserid/static/jquery/controller/pages/listening.js rename to resources/static/jquery/controller/pages/listening.js diff --git a/browserid/static/jquery/controller/pages/plugin.js b/resources/static/jquery/controller/pages/plugin.js similarity index 100% rename from browserid/static/jquery/controller/pages/plugin.js rename to resources/static/jquery/controller/pages/plugin.js diff --git a/browserid/static/jquery/controller/qunit.html b/resources/static/jquery/controller/qunit.html similarity index 100% rename from browserid/static/jquery/controller/qunit.html rename to resources/static/jquery/controller/qunit.html diff --git a/browserid/static/jquery/controller/subscribe/funcunit.html b/resources/static/jquery/controller/subscribe/funcunit.html similarity index 100% rename from browserid/static/jquery/controller/subscribe/funcunit.html rename to resources/static/jquery/controller/subscribe/funcunit.html diff --git a/browserid/static/jquery/controller/subscribe/subscribe.html b/resources/static/jquery/controller/subscribe/subscribe.html similarity index 100% rename from browserid/static/jquery/controller/subscribe/subscribe.html rename to resources/static/jquery/controller/subscribe/subscribe.html diff --git a/browserid/static/jquery/controller/subscribe/subscribe.js b/resources/static/jquery/controller/subscribe/subscribe.js similarity index 100% rename from browserid/static/jquery/controller/subscribe/subscribe.js rename to resources/static/jquery/controller/subscribe/subscribe.js diff --git a/browserid/static/jquery/controller/view/qunit.html b/resources/static/jquery/controller/view/qunit.html similarity index 100% rename from browserid/static/jquery/controller/view/qunit.html rename to resources/static/jquery/controller/view/qunit.html diff --git a/browserid/static/jquery/controller/view/test/qunit/controller_view_test.js b/resources/static/jquery/controller/view/test/qunit/controller_view_test.js similarity index 100% rename from browserid/static/jquery/controller/view/test/qunit/controller_view_test.js rename to resources/static/jquery/controller/view/test/qunit/controller_view_test.js diff --git a/browserid/static/jquery/controller/view/test/qunit/qunit.js b/resources/static/jquery/controller/view/test/qunit/qunit.js similarity index 100% rename from browserid/static/jquery/controller/view/test/qunit/qunit.js rename to resources/static/jquery/controller/view/test/qunit/qunit.js diff --git a/browserid/static/jquery/controller/view/test/qunit/views/init.micro b/resources/static/jquery/controller/view/test/qunit/views/init.micro similarity index 100% rename from browserid/static/jquery/controller/view/test/qunit/views/init.micro rename to resources/static/jquery/controller/view/test/qunit/views/init.micro diff --git a/browserid/static/jquery/controller/view/view.js b/resources/static/jquery/controller/view/view.js similarity index 100% rename from browserid/static/jquery/controller/view/view.js rename to resources/static/jquery/controller/view/view.js diff --git a/browserid/static/jquery/dom/closest/closest.js b/resources/static/jquery/dom/closest/closest.js similarity index 100% rename from browserid/static/jquery/dom/closest/closest.js rename to resources/static/jquery/dom/closest/closest.js diff --git a/browserid/static/jquery/dom/compare/compare.html b/resources/static/jquery/dom/compare/compare.html similarity index 100% rename from browserid/static/jquery/dom/compare/compare.html rename to resources/static/jquery/dom/compare/compare.html diff --git a/browserid/static/jquery/dom/compare/compare.js b/resources/static/jquery/dom/compare/compare.js similarity index 100% rename from browserid/static/jquery/dom/compare/compare.js rename to resources/static/jquery/dom/compare/compare.js diff --git a/browserid/static/jquery/dom/compare/compare_test.js b/resources/static/jquery/dom/compare/compare_test.js similarity index 100% rename from browserid/static/jquery/dom/compare/compare_test.js rename to resources/static/jquery/dom/compare/compare_test.js diff --git a/browserid/static/jquery/dom/compare/qunit.html b/resources/static/jquery/dom/compare/qunit.html similarity index 100% rename from browserid/static/jquery/dom/compare/qunit.html rename to resources/static/jquery/dom/compare/qunit.html diff --git a/browserid/static/jquery/dom/cookie/cookie.js b/resources/static/jquery/dom/cookie/cookie.js similarity index 100% rename from browserid/static/jquery/dom/cookie/cookie.js rename to resources/static/jquery/dom/cookie/cookie.js diff --git a/browserid/static/jquery/dom/cur_styles/cur_styles.html b/resources/static/jquery/dom/cur_styles/cur_styles.html similarity index 100% rename from browserid/static/jquery/dom/cur_styles/cur_styles.html rename to resources/static/jquery/dom/cur_styles/cur_styles.html diff --git a/browserid/static/jquery/dom/cur_styles/cur_styles.js b/resources/static/jquery/dom/cur_styles/cur_styles.js similarity index 100% rename from browserid/static/jquery/dom/cur_styles/cur_styles.js rename to resources/static/jquery/dom/cur_styles/cur_styles.js diff --git a/browserid/static/jquery/dom/cur_styles/cur_styles_test.js b/resources/static/jquery/dom/cur_styles/cur_styles_test.js similarity index 100% rename from browserid/static/jquery/dom/cur_styles/cur_styles_test.js rename to resources/static/jquery/dom/cur_styles/cur_styles_test.js diff --git a/browserid/static/jquery/dom/cur_styles/qunit.html b/resources/static/jquery/dom/cur_styles/qunit.html similarity index 100% rename from browserid/static/jquery/dom/cur_styles/qunit.html rename to resources/static/jquery/dom/cur_styles/qunit.html diff --git a/browserid/static/jquery/dom/cur_styles/test/curStyles.micro b/resources/static/jquery/dom/cur_styles/test/curStyles.micro similarity index 100% rename from browserid/static/jquery/dom/cur_styles/test/curStyles.micro rename to resources/static/jquery/dom/cur_styles/test/curStyles.micro diff --git a/browserid/static/jquery/dom/dimensions/dimensions.html b/resources/static/jquery/dom/dimensions/dimensions.html similarity index 100% rename from browserid/static/jquery/dom/dimensions/dimensions.html rename to resources/static/jquery/dom/dimensions/dimensions.html diff --git a/browserid/static/jquery/dom/dimensions/dimensions.js b/resources/static/jquery/dom/dimensions/dimensions.js similarity index 100% rename from browserid/static/jquery/dom/dimensions/dimensions.js rename to resources/static/jquery/dom/dimensions/dimensions.js diff --git a/browserid/static/jquery/dom/dimensions/qunit.html b/resources/static/jquery/dom/dimensions/qunit.html similarity index 100% rename from browserid/static/jquery/dom/dimensions/qunit.html rename to resources/static/jquery/dom/dimensions/qunit.html diff --git a/browserid/static/jquery/dom/dimensions/test/qunit/curStyles.micro b/resources/static/jquery/dom/dimensions/test/qunit/curStyles.micro similarity index 100% rename from browserid/static/jquery/dom/dimensions/test/qunit/curStyles.micro rename to resources/static/jquery/dom/dimensions/test/qunit/curStyles.micro diff --git a/browserid/static/jquery/dom/dimensions/test/qunit/dimensions_test.js b/resources/static/jquery/dom/dimensions/test/qunit/dimensions_test.js similarity index 100% rename from browserid/static/jquery/dom/dimensions/test/qunit/dimensions_test.js rename to resources/static/jquery/dom/dimensions/test/qunit/dimensions_test.js diff --git a/browserid/static/jquery/dom/dimensions/test/qunit/outer.micro b/resources/static/jquery/dom/dimensions/test/qunit/outer.micro similarity index 100% rename from browserid/static/jquery/dom/dimensions/test/qunit/outer.micro rename to resources/static/jquery/dom/dimensions/test/qunit/outer.micro diff --git a/browserid/static/jquery/dom/dimensions/test/qunit/qunit.js b/resources/static/jquery/dom/dimensions/test/qunit/qunit.js similarity index 100% rename from browserid/static/jquery/dom/dimensions/test/qunit/qunit.js rename to resources/static/jquery/dom/dimensions/test/qunit/qunit.js diff --git a/browserid/static/jquery/dom/dom.js b/resources/static/jquery/dom/dom.js similarity index 100% rename from browserid/static/jquery/dom/dom.js rename to resources/static/jquery/dom/dom.js diff --git a/browserid/static/jquery/dom/fixture/fixture.html b/resources/static/jquery/dom/fixture/fixture.html similarity index 100% rename from browserid/static/jquery/dom/fixture/fixture.html rename to resources/static/jquery/dom/fixture/fixture.html diff --git a/browserid/static/jquery/dom/fixture/fixture.js b/resources/static/jquery/dom/fixture/fixture.js similarity index 100% rename from browserid/static/jquery/dom/fixture/fixture.js rename to resources/static/jquery/dom/fixture/fixture.js diff --git a/browserid/static/jquery/dom/fixture/fixture_test.js b/resources/static/jquery/dom/fixture/fixture_test.js similarity index 100% rename from browserid/static/jquery/dom/fixture/fixture_test.js rename to resources/static/jquery/dom/fixture/fixture_test.js diff --git a/browserid/static/jquery/dom/fixture/fixtures/foo.json b/resources/static/jquery/dom/fixture/fixtures/foo.json similarity index 100% rename from browserid/static/jquery/dom/fixture/fixtures/foo.json rename to resources/static/jquery/dom/fixture/fixtures/foo.json diff --git a/browserid/static/jquery/dom/fixture/fixtures/foobar.json b/resources/static/jquery/dom/fixture/fixtures/foobar.json similarity index 100% rename from browserid/static/jquery/dom/fixture/fixtures/foobar.json rename to resources/static/jquery/dom/fixture/fixtures/foobar.json diff --git a/browserid/static/jquery/dom/fixture/fixtures/messages.html b/resources/static/jquery/dom/fixture/fixtures/messages.html similarity index 100% rename from browserid/static/jquery/dom/fixture/fixtures/messages.html rename to resources/static/jquery/dom/fixture/fixtures/messages.html diff --git a/browserid/static/jquery/dom/fixture/fixtures/test.json b/resources/static/jquery/dom/fixture/fixtures/test.json similarity index 100% rename from browserid/static/jquery/dom/fixture/fixtures/test.json rename to resources/static/jquery/dom/fixture/fixtures/test.json diff --git a/browserid/static/jquery/dom/fixture/qunit.html b/resources/static/jquery/dom/fixture/qunit.html similarity index 100% rename from browserid/static/jquery/dom/fixture/qunit.html rename to resources/static/jquery/dom/fixture/qunit.html diff --git a/browserid/static/jquery/dom/form_params/form_params.html b/resources/static/jquery/dom/form_params/form_params.html similarity index 100% rename from browserid/static/jquery/dom/form_params/form_params.html rename to resources/static/jquery/dom/form_params/form_params.html diff --git a/browserid/static/jquery/dom/form_params/form_params.js b/resources/static/jquery/dom/form_params/form_params.js similarity index 100% rename from browserid/static/jquery/dom/form_params/form_params.js rename to resources/static/jquery/dom/form_params/form_params.js diff --git a/browserid/static/jquery/dom/form_params/qunit.html b/resources/static/jquery/dom/form_params/qunit.html similarity index 100% rename from browserid/static/jquery/dom/form_params/qunit.html rename to resources/static/jquery/dom/form_params/qunit.html diff --git a/browserid/static/jquery/dom/form_params/test/qunit/basics.micro b/resources/static/jquery/dom/form_params/test/qunit/basics.micro similarity index 100% rename from browserid/static/jquery/dom/form_params/test/qunit/basics.micro rename to resources/static/jquery/dom/form_params/test/qunit/basics.micro diff --git a/browserid/static/jquery/dom/form_params/test/qunit/checkbox.micro b/resources/static/jquery/dom/form_params/test/qunit/checkbox.micro similarity index 100% rename from browserid/static/jquery/dom/form_params/test/qunit/checkbox.micro rename to resources/static/jquery/dom/form_params/test/qunit/checkbox.micro diff --git a/browserid/static/jquery/dom/form_params/test/qunit/form_params_test.js b/resources/static/jquery/dom/form_params/test/qunit/form_params_test.js similarity index 100% rename from browserid/static/jquery/dom/form_params/test/qunit/form_params_test.js rename to resources/static/jquery/dom/form_params/test/qunit/form_params_test.js diff --git a/browserid/static/jquery/dom/form_params/test/qunit/qunit.js b/resources/static/jquery/dom/form_params/test/qunit/qunit.js similarity index 100% rename from browserid/static/jquery/dom/form_params/test/qunit/qunit.js rename to resources/static/jquery/dom/form_params/test/qunit/qunit.js diff --git a/browserid/static/jquery/dom/form_params/test/qunit/truthy.micro b/resources/static/jquery/dom/form_params/test/qunit/truthy.micro similarity index 100% rename from browserid/static/jquery/dom/form_params/test/qunit/truthy.micro rename to resources/static/jquery/dom/form_params/test/qunit/truthy.micro diff --git a/browserid/static/jquery/dom/range/qunit.html b/resources/static/jquery/dom/range/qunit.html similarity index 100% rename from browserid/static/jquery/dom/range/qunit.html rename to resources/static/jquery/dom/range/qunit.html diff --git a/browserid/static/jquery/dom/range/range.html b/resources/static/jquery/dom/range/range.html similarity index 100% rename from browserid/static/jquery/dom/range/range.html rename to resources/static/jquery/dom/range/range.html diff --git a/browserid/static/jquery/dom/range/range.js b/resources/static/jquery/dom/range/range.js similarity index 100% rename from browserid/static/jquery/dom/range/range.js rename to resources/static/jquery/dom/range/range.js diff --git a/browserid/static/jquery/dom/range/range_test.js b/resources/static/jquery/dom/range/range_test.js similarity index 100% rename from browserid/static/jquery/dom/range/range_test.js rename to resources/static/jquery/dom/range/range_test.js diff --git a/browserid/static/jquery/dom/selection/qunit.html b/resources/static/jquery/dom/selection/qunit.html similarity index 100% rename from browserid/static/jquery/dom/selection/qunit.html rename to resources/static/jquery/dom/selection/qunit.html diff --git a/browserid/static/jquery/dom/selection/selection.html b/resources/static/jquery/dom/selection/selection.html similarity index 100% rename from browserid/static/jquery/dom/selection/selection.html rename to resources/static/jquery/dom/selection/selection.html diff --git a/browserid/static/jquery/dom/selection/selection.js b/resources/static/jquery/dom/selection/selection.js similarity index 100% rename from browserid/static/jquery/dom/selection/selection.js rename to resources/static/jquery/dom/selection/selection.js diff --git a/browserid/static/jquery/dom/selection/selection_test.js b/resources/static/jquery/dom/selection/selection_test.js similarity index 100% rename from browserid/static/jquery/dom/selection/selection_test.js rename to resources/static/jquery/dom/selection/selection_test.js diff --git a/browserid/static/jquery/dom/within/within.js b/resources/static/jquery/dom/within/within.js similarity index 100% rename from browserid/static/jquery/dom/within/within.js rename to resources/static/jquery/dom/within/within.js diff --git a/browserid/static/jquery/download/btn.png b/resources/static/jquery/download/btn.png similarity index 100% rename from browserid/static/jquery/download/btn.png rename to resources/static/jquery/download/btn.png diff --git a/browserid/static/jquery/download/dependencies.json b/resources/static/jquery/download/dependencies.json similarity index 100% rename from browserid/static/jquery/download/dependencies.json rename to resources/static/jquery/download/dependencies.json diff --git a/browserid/static/jquery/download/download.css b/resources/static/jquery/download/download.css similarity index 100% rename from browserid/static/jquery/download/download.css rename to resources/static/jquery/download/download.css diff --git a/browserid/static/jquery/download/download.html b/resources/static/jquery/download/download.html similarity index 100% rename from browserid/static/jquery/download/download.html rename to resources/static/jquery/download/download.html diff --git a/browserid/static/jquery/download/download.js b/resources/static/jquery/download/download.js similarity index 100% rename from browserid/static/jquery/download/download.js rename to resources/static/jquery/download/download.js diff --git a/browserid/static/jquery/download/test/controllerpage.html b/resources/static/jquery/download/test/controllerpage.html similarity index 100% rename from browserid/static/jquery/download/test/controllerpage.html rename to resources/static/jquery/download/test/controllerpage.html diff --git a/browserid/static/jquery/download/test/jquery-1.4.3.js b/resources/static/jquery/download/test/jquery-1.4.3.js similarity index 100% rename from browserid/static/jquery/download/test/jquery-1.4.3.js rename to resources/static/jquery/download/test/jquery-1.4.3.js diff --git a/browserid/static/jquery/download/test/run.js b/resources/static/jquery/download/test/run.js similarity index 100% rename from browserid/static/jquery/download/test/run.js rename to resources/static/jquery/download/test/run.js diff --git a/browserid/static/jquery/event/default/default.html b/resources/static/jquery/event/default/default.html similarity index 100% rename from browserid/static/jquery/event/default/default.html rename to resources/static/jquery/event/default/default.html diff --git a/browserid/static/jquery/event/default/default.js b/resources/static/jquery/event/default/default.js similarity index 100% rename from browserid/static/jquery/event/default/default.js rename to resources/static/jquery/event/default/default.js diff --git a/browserid/static/jquery/event/default/default_pause_test.html b/resources/static/jquery/event/default/default_pause_test.html similarity index 100% rename from browserid/static/jquery/event/default/default_pause_test.html rename to resources/static/jquery/event/default/default_pause_test.html diff --git a/browserid/static/jquery/event/default/default_pause_test.js b/resources/static/jquery/event/default/default_pause_test.js similarity index 100% rename from browserid/static/jquery/event/default/default_pause_test.js rename to resources/static/jquery/event/default/default_pause_test.js diff --git a/browserid/static/jquery/event/default/default_test.js b/resources/static/jquery/event/default/default_test.js similarity index 100% rename from browserid/static/jquery/event/default/default_test.js rename to resources/static/jquery/event/default/default_test.js diff --git a/browserid/static/jquery/event/default/defaultjquery.html b/resources/static/jquery/event/default/defaultjquery.html similarity index 100% rename from browserid/static/jquery/event/default/defaultjquery.html rename to resources/static/jquery/event/default/defaultjquery.html diff --git a/browserid/static/jquery/event/default/qunit.html b/resources/static/jquery/event/default/qunit.html similarity index 100% rename from browserid/static/jquery/event/default/qunit.html rename to resources/static/jquery/event/default/qunit.html diff --git a/browserid/static/jquery/event/destroyed/destroyed.html b/resources/static/jquery/event/destroyed/destroyed.html similarity index 100% rename from browserid/static/jquery/event/destroyed/destroyed.html rename to resources/static/jquery/event/destroyed/destroyed.html diff --git a/browserid/static/jquery/event/destroyed/destroyed.js b/resources/static/jquery/event/destroyed/destroyed.js similarity index 100% rename from browserid/static/jquery/event/destroyed/destroyed.js rename to resources/static/jquery/event/destroyed/destroyed.js diff --git a/browserid/static/jquery/event/destroyed/destroyed_menu.html b/resources/static/jquery/event/destroyed/destroyed_menu.html similarity index 100% rename from browserid/static/jquery/event/destroyed/destroyed_menu.html rename to resources/static/jquery/event/destroyed/destroyed_menu.html diff --git a/browserid/static/jquery/event/destroyed/qunit.html b/resources/static/jquery/event/destroyed/qunit.html similarity index 100% rename from browserid/static/jquery/event/destroyed/qunit.html rename to resources/static/jquery/event/destroyed/qunit.html diff --git a/browserid/static/jquery/event/destroyed/test/qunit/destroyed_test.js b/resources/static/jquery/event/destroyed/test/qunit/destroyed_test.js similarity index 100% rename from browserid/static/jquery/event/destroyed/test/qunit/destroyed_test.js rename to resources/static/jquery/event/destroyed/test/qunit/destroyed_test.js diff --git a/browserid/static/jquery/event/destroyed/test/qunit/qunit.js b/resources/static/jquery/event/destroyed/test/qunit/qunit.js similarity index 100% rename from browserid/static/jquery/event/destroyed/test/qunit/qunit.js rename to resources/static/jquery/event/destroyed/test/qunit/qunit.js diff --git a/browserid/static/jquery/event/drag/drag.html b/resources/static/jquery/event/drag/drag.html similarity index 100% rename from browserid/static/jquery/event/drag/drag.html rename to resources/static/jquery/event/drag/drag.html diff --git a/browserid/static/jquery/event/drag/drag.js b/resources/static/jquery/event/drag/drag.js similarity index 100% rename from browserid/static/jquery/event/drag/drag.js rename to resources/static/jquery/event/drag/drag.js diff --git a/browserid/static/jquery/event/drag/drag_test.js b/resources/static/jquery/event/drag/drag_test.js similarity index 100% rename from browserid/static/jquery/event/drag/drag_test.js rename to resources/static/jquery/event/drag/drag_test.js diff --git a/browserid/static/jquery/event/drag/limit/limit.html b/resources/static/jquery/event/drag/limit/limit.html similarity index 100% rename from browserid/static/jquery/event/drag/limit/limit.html rename to resources/static/jquery/event/drag/limit/limit.html diff --git a/browserid/static/jquery/event/drag/limit/limit.js b/resources/static/jquery/event/drag/limit/limit.js similarity index 100% rename from browserid/static/jquery/event/drag/limit/limit.js rename to resources/static/jquery/event/drag/limit/limit.js diff --git a/browserid/static/jquery/event/drag/qunit.html b/resources/static/jquery/event/drag/qunit.html similarity index 100% rename from browserid/static/jquery/event/drag/qunit.html rename to resources/static/jquery/event/drag/qunit.html diff --git a/browserid/static/jquery/event/drag/scroll/scroll.js b/resources/static/jquery/event/drag/scroll/scroll.js similarity index 100% rename from browserid/static/jquery/event/drag/scroll/scroll.js rename to resources/static/jquery/event/drag/scroll/scroll.js diff --git a/browserid/static/jquery/event/drag/step/step.html b/resources/static/jquery/event/drag/step/step.html similarity index 100% rename from browserid/static/jquery/event/drag/step/step.html rename to resources/static/jquery/event/drag/step/step.html diff --git a/browserid/static/jquery/event/drag/step/step.js b/resources/static/jquery/event/drag/step/step.js similarity index 100% rename from browserid/static/jquery/event/drag/step/step.js rename to resources/static/jquery/event/drag/step/step.js diff --git a/browserid/static/jquery/event/drop/drop.html b/resources/static/jquery/event/drop/drop.html similarity index 100% rename from browserid/static/jquery/event/drop/drop.html rename to resources/static/jquery/event/drop/drop.html diff --git a/browserid/static/jquery/event/drop/drop.js b/resources/static/jquery/event/drop/drop.js similarity index 100% rename from browserid/static/jquery/event/drop/drop.js rename to resources/static/jquery/event/drop/drop.js diff --git a/browserid/static/jquery/event/drop/drop_test.js b/resources/static/jquery/event/drop/drop_test.js similarity index 100% rename from browserid/static/jquery/event/drop/drop_test.js rename to resources/static/jquery/event/drop/drop_test.js diff --git a/browserid/static/jquery/event/event.js b/resources/static/jquery/event/event.js similarity index 100% rename from browserid/static/jquery/event/event.js rename to resources/static/jquery/event/event.js diff --git a/browserid/static/jquery/event/handle/handle.js b/resources/static/jquery/event/handle/handle.js similarity index 100% rename from browserid/static/jquery/event/handle/handle.js rename to resources/static/jquery/event/handle/handle.js diff --git a/browserid/static/jquery/event/hashchange/hashchange.js b/resources/static/jquery/event/hashchange/hashchange.js similarity index 100% rename from browserid/static/jquery/event/hashchange/hashchange.js rename to resources/static/jquery/event/hashchange/hashchange.js diff --git a/browserid/static/jquery/event/hover/hover.html b/resources/static/jquery/event/hover/hover.html similarity index 100% rename from browserid/static/jquery/event/hover/hover.html rename to resources/static/jquery/event/hover/hover.html diff --git a/browserid/static/jquery/event/hover/hover.js b/resources/static/jquery/event/hover/hover.js similarity index 100% rename from browserid/static/jquery/event/hover/hover.js rename to resources/static/jquery/event/hover/hover.js diff --git a/browserid/static/jquery/event/hover/qunit.html b/resources/static/jquery/event/hover/qunit.html similarity index 100% rename from browserid/static/jquery/event/hover/qunit.html rename to resources/static/jquery/event/hover/qunit.html diff --git a/browserid/static/jquery/event/hover/test/qunit/hover_test.js b/resources/static/jquery/event/hover/test/qunit/hover_test.js similarity index 100% rename from browserid/static/jquery/event/hover/test/qunit/hover_test.js rename to resources/static/jquery/event/hover/test/qunit/hover_test.js diff --git a/browserid/static/jquery/event/hover/test/qunit/qunit.js b/resources/static/jquery/event/hover/test/qunit/qunit.js similarity index 100% rename from browserid/static/jquery/event/hover/test/qunit/qunit.js rename to resources/static/jquery/event/hover/test/qunit/qunit.js diff --git a/browserid/static/jquery/event/key/key.html b/resources/static/jquery/event/key/key.html similarity index 100% rename from browserid/static/jquery/event/key/key.html rename to resources/static/jquery/event/key/key.html diff --git a/browserid/static/jquery/event/key/key.js b/resources/static/jquery/event/key/key.js similarity index 100% rename from browserid/static/jquery/event/key/key.js rename to resources/static/jquery/event/key/key.js diff --git a/browserid/static/jquery/event/key/key_test.js b/resources/static/jquery/event/key/key_test.js similarity index 100% rename from browserid/static/jquery/event/key/key_test.js rename to resources/static/jquery/event/key/key_test.js diff --git a/browserid/static/jquery/event/key/qunit.html b/resources/static/jquery/event/key/qunit.html similarity index 100% rename from browserid/static/jquery/event/key/qunit.html rename to resources/static/jquery/event/key/qunit.html diff --git a/browserid/static/jquery/event/livehack/livehack.js b/resources/static/jquery/event/livehack/livehack.js similarity index 100% rename from browserid/static/jquery/event/livehack/livehack.js rename to resources/static/jquery/event/livehack/livehack.js diff --git a/browserid/static/jquery/event/pause/pause.html b/resources/static/jquery/event/pause/pause.html similarity index 100% rename from browserid/static/jquery/event/pause/pause.html rename to resources/static/jquery/event/pause/pause.html diff --git a/browserid/static/jquery/event/pause/pause.js b/resources/static/jquery/event/pause/pause.js similarity index 100% rename from browserid/static/jquery/event/pause/pause.js rename to resources/static/jquery/event/pause/pause.js diff --git a/browserid/static/jquery/event/pause/pause_test.js b/resources/static/jquery/event/pause/pause_test.js similarity index 100% rename from browserid/static/jquery/event/pause/pause_test.js rename to resources/static/jquery/event/pause/pause_test.js diff --git a/browserid/static/jquery/event/pause/qunit.html b/resources/static/jquery/event/pause/qunit.html similarity index 100% rename from browserid/static/jquery/event/pause/qunit.html rename to resources/static/jquery/event/pause/qunit.html diff --git a/browserid/static/jquery/event/resize/demo.html b/resources/static/jquery/event/resize/demo.html similarity index 100% rename from browserid/static/jquery/event/resize/demo.html rename to resources/static/jquery/event/resize/demo.html diff --git a/browserid/static/jquery/event/resize/qunit.html b/resources/static/jquery/event/resize/qunit.html similarity index 100% rename from browserid/static/jquery/event/resize/qunit.html rename to resources/static/jquery/event/resize/qunit.html diff --git a/browserid/static/jquery/event/resize/resize.html b/resources/static/jquery/event/resize/resize.html similarity index 100% rename from browserid/static/jquery/event/resize/resize.html rename to resources/static/jquery/event/resize/resize.html diff --git a/browserid/static/jquery/event/resize/resize.js b/resources/static/jquery/event/resize/resize.js similarity index 100% rename from browserid/static/jquery/event/resize/resize.js rename to resources/static/jquery/event/resize/resize.js diff --git a/browserid/static/jquery/event/resize/resize_test.js b/resources/static/jquery/event/resize/resize_test.js similarity index 100% rename from browserid/static/jquery/event/resize/resize_test.js rename to resources/static/jquery/event/resize/resize_test.js diff --git a/browserid/static/jquery/event/selection/qunit.html b/resources/static/jquery/event/selection/qunit.html similarity index 100% rename from browserid/static/jquery/event/selection/qunit.html rename to resources/static/jquery/event/selection/qunit.html diff --git a/browserid/static/jquery/event/selection/selection.html b/resources/static/jquery/event/selection/selection.html similarity index 100% rename from browserid/static/jquery/event/selection/selection.html rename to resources/static/jquery/event/selection/selection.html diff --git a/browserid/static/jquery/event/selection/selection.js b/resources/static/jquery/event/selection/selection.js similarity index 100% rename from browserid/static/jquery/event/selection/selection.js rename to resources/static/jquery/event/selection/selection.js diff --git a/browserid/static/jquery/event/selection/selection_test.js b/resources/static/jquery/event/selection/selection_test.js similarity index 100% rename from browserid/static/jquery/event/selection/selection_test.js rename to resources/static/jquery/event/selection/selection_test.js diff --git a/browserid/static/jquery/event/swipe/qunit.html b/resources/static/jquery/event/swipe/qunit.html similarity index 100% rename from browserid/static/jquery/event/swipe/qunit.html rename to resources/static/jquery/event/swipe/qunit.html diff --git a/browserid/static/jquery/event/swipe/swipe.html b/resources/static/jquery/event/swipe/swipe.html similarity index 100% rename from browserid/static/jquery/event/swipe/swipe.html rename to resources/static/jquery/event/swipe/swipe.html diff --git a/browserid/static/jquery/event/swipe/swipe.js b/resources/static/jquery/event/swipe/swipe.js similarity index 100% rename from browserid/static/jquery/event/swipe/swipe.js rename to resources/static/jquery/event/swipe/swipe.js diff --git a/browserid/static/jquery/event/swipe/swipe_test.js b/resources/static/jquery/event/swipe/swipe_test.js similarity index 100% rename from browserid/static/jquery/event/swipe/swipe_test.js rename to resources/static/jquery/event/swipe/swipe_test.js diff --git a/browserid/static/jquery/generate/app b/resources/static/jquery/generate/app similarity index 100% rename from browserid/static/jquery/generate/app rename to resources/static/jquery/generate/app diff --git a/browserid/static/jquery/generate/controller b/resources/static/jquery/generate/controller similarity index 100% rename from browserid/static/jquery/generate/controller rename to resources/static/jquery/generate/controller diff --git a/browserid/static/jquery/generate/funcunit b/resources/static/jquery/generate/funcunit similarity index 100% rename from browserid/static/jquery/generate/funcunit rename to resources/static/jquery/generate/funcunit diff --git a/browserid/static/jquery/generate/model b/resources/static/jquery/generate/model similarity index 100% rename from browserid/static/jquery/generate/model rename to resources/static/jquery/generate/model diff --git a/browserid/static/jquery/generate/page b/resources/static/jquery/generate/page similarity index 100% rename from browserid/static/jquery/generate/page rename to resources/static/jquery/generate/page diff --git a/browserid/static/jquery/generate/plugin b/resources/static/jquery/generate/plugin similarity index 100% rename from browserid/static/jquery/generate/plugin rename to resources/static/jquery/generate/plugin diff --git a/browserid/static/jquery/generate/scaffold b/resources/static/jquery/generate/scaffold similarity index 100% rename from browserid/static/jquery/generate/scaffold rename to resources/static/jquery/generate/scaffold diff --git a/browserid/static/jquery/generate/templates/app/(application_name).css.ejs b/resources/static/jquery/generate/templates/app/(application_name).css.ejs similarity index 100% rename from browserid/static/jquery/generate/templates/app/(application_name).css.ejs rename to resources/static/jquery/generate/templates/app/(application_name).css.ejs diff --git a/browserid/static/jquery/generate/templates/app/(application_name).html.ejs b/resources/static/jquery/generate/templates/app/(application_name).html.ejs similarity index 100% rename from browserid/static/jquery/generate/templates/app/(application_name).html.ejs rename to resources/static/jquery/generate/templates/app/(application_name).html.ejs diff --git a/browserid/static/jquery/generate/templates/app/(application_name).js.ejs b/resources/static/jquery/generate/templates/app/(application_name).js.ejs similarity index 100% rename from browserid/static/jquery/generate/templates/app/(application_name).js.ejs rename to resources/static/jquery/generate/templates/app/(application_name).js.ejs diff --git a/browserid/static/jquery/generate/templates/app/controllers/.ignore b/resources/static/jquery/generate/templates/app/controllers/.ignore similarity index 100% rename from browserid/static/jquery/generate/templates/app/controllers/.ignore rename to resources/static/jquery/generate/templates/app/controllers/.ignore diff --git a/browserid/static/jquery/generate/templates/app/docs/.ignore b/resources/static/jquery/generate/templates/app/docs/.ignore similarity index 100% rename from browserid/static/jquery/generate/templates/app/docs/.ignore rename to resources/static/jquery/generate/templates/app/docs/.ignore diff --git a/browserid/static/jquery/generate/templates/app/fixtures/.ignore b/resources/static/jquery/generate/templates/app/fixtures/.ignore similarity index 100% rename from browserid/static/jquery/generate/templates/app/fixtures/.ignore rename to resources/static/jquery/generate/templates/app/fixtures/.ignore diff --git a/browserid/static/jquery/generate/templates/app/funcunit.html.ejs b/resources/static/jquery/generate/templates/app/funcunit.html.ejs similarity index 100% rename from browserid/static/jquery/generate/templates/app/funcunit.html.ejs rename to resources/static/jquery/generate/templates/app/funcunit.html.ejs diff --git a/browserid/static/jquery/generate/templates/app/models/.ignore b/resources/static/jquery/generate/templates/app/models/.ignore similarity index 100% rename from browserid/static/jquery/generate/templates/app/models/.ignore rename to resources/static/jquery/generate/templates/app/models/.ignore diff --git a/browserid/static/jquery/generate/templates/app/qunit.html.ejs b/resources/static/jquery/generate/templates/app/qunit.html.ejs similarity index 100% rename from browserid/static/jquery/generate/templates/app/qunit.html.ejs rename to resources/static/jquery/generate/templates/app/qunit.html.ejs diff --git a/browserid/static/jquery/generate/templates/app/resources/.ignore b/resources/static/jquery/generate/templates/app/resources/.ignore similarity index 100% rename from browserid/static/jquery/generate/templates/app/resources/.ignore rename to resources/static/jquery/generate/templates/app/resources/.ignore diff --git a/browserid/static/jquery/generate/templates/app/scripts/build.html.ejs b/resources/static/jquery/generate/templates/app/scripts/build.html.ejs similarity index 100% rename from browserid/static/jquery/generate/templates/app/scripts/build.html.ejs rename to resources/static/jquery/generate/templates/app/scripts/build.html.ejs diff --git a/browserid/static/jquery/generate/templates/app/scripts/build.js.ejs b/resources/static/jquery/generate/templates/app/scripts/build.js.ejs similarity index 100% rename from browserid/static/jquery/generate/templates/app/scripts/build.js.ejs rename to resources/static/jquery/generate/templates/app/scripts/build.js.ejs diff --git a/browserid/static/jquery/generate/templates/app/scripts/clean.js.ejs b/resources/static/jquery/generate/templates/app/scripts/clean.js.ejs similarity index 100% rename from browserid/static/jquery/generate/templates/app/scripts/clean.js.ejs rename to resources/static/jquery/generate/templates/app/scripts/clean.js.ejs diff --git a/browserid/static/jquery/generate/templates/app/scripts/docs.js.ejs b/resources/static/jquery/generate/templates/app/scripts/docs.js.ejs similarity index 100% rename from browserid/static/jquery/generate/templates/app/scripts/docs.js.ejs rename to resources/static/jquery/generate/templates/app/scripts/docs.js.ejs diff --git a/browserid/static/jquery/generate/templates/app/test/funcunit/(application_name)_test.js.ejs b/resources/static/jquery/generate/templates/app/test/funcunit/(application_name)_test.js.ejs similarity index 100% rename from browserid/static/jquery/generate/templates/app/test/funcunit/(application_name)_test.js.ejs rename to resources/static/jquery/generate/templates/app/test/funcunit/(application_name)_test.js.ejs diff --git a/browserid/static/jquery/generate/templates/app/test/funcunit/funcunit.js.ejs b/resources/static/jquery/generate/templates/app/test/funcunit/funcunit.js.ejs similarity index 100% rename from browserid/static/jquery/generate/templates/app/test/funcunit/funcunit.js.ejs rename to resources/static/jquery/generate/templates/app/test/funcunit/funcunit.js.ejs diff --git a/browserid/static/jquery/generate/templates/app/test/qunit/(application_name)_test.js.ejs b/resources/static/jquery/generate/templates/app/test/qunit/(application_name)_test.js.ejs similarity index 100% rename from browserid/static/jquery/generate/templates/app/test/qunit/(application_name)_test.js.ejs rename to resources/static/jquery/generate/templates/app/test/qunit/(application_name)_test.js.ejs diff --git a/browserid/static/jquery/generate/templates/app/test/qunit/qunit.js.ejs b/resources/static/jquery/generate/templates/app/test/qunit/qunit.js.ejs similarity index 100% rename from browserid/static/jquery/generate/templates/app/test/qunit/qunit.js.ejs rename to resources/static/jquery/generate/templates/app/test/qunit/qunit.js.ejs diff --git a/browserid/static/jquery/generate/templates/app/views/.ignore b/resources/static/jquery/generate/templates/app/views/.ignore similarity index 100% rename from browserid/static/jquery/generate/templates/app/views/.ignore rename to resources/static/jquery/generate/templates/app/views/.ignore diff --git a/browserid/static/jquery/generate/templates/controller/(underscore).html.ejs b/resources/static/jquery/generate/templates/controller/(underscore).html.ejs similarity index 100% rename from browserid/static/jquery/generate/templates/controller/(underscore).html.ejs rename to resources/static/jquery/generate/templates/controller/(underscore).html.ejs diff --git a/browserid/static/jquery/generate/templates/controller/(underscore).js.ejs b/resources/static/jquery/generate/templates/controller/(underscore).js.ejs similarity index 100% rename from browserid/static/jquery/generate/templates/controller/(underscore).js.ejs rename to resources/static/jquery/generate/templates/controller/(underscore).js.ejs diff --git a/browserid/static/jquery/generate/templates/controller/(underscore)_test.js.ejs b/resources/static/jquery/generate/templates/controller/(underscore)_test.js.ejs similarity index 100% rename from browserid/static/jquery/generate/templates/controller/(underscore)_test.js.ejs rename to resources/static/jquery/generate/templates/controller/(underscore)_test.js.ejs diff --git a/browserid/static/jquery/generate/templates/controller/funcunit.html.ejs b/resources/static/jquery/generate/templates/controller/funcunit.html.ejs similarity index 100% rename from browserid/static/jquery/generate/templates/controller/funcunit.html.ejs rename to resources/static/jquery/generate/templates/controller/funcunit.html.ejs diff --git a/browserid/static/jquery/generate/templates/controller/views/.ignore b/resources/static/jquery/generate/templates/controller/views/.ignore similarity index 100% rename from browserid/static/jquery/generate/templates/controller/views/.ignore rename to resources/static/jquery/generate/templates/controller/views/.ignore diff --git a/browserid/static/jquery/generate/templates/model/fixtures.link b/resources/static/jquery/generate/templates/model/fixtures.link similarity index 100% rename from browserid/static/jquery/generate/templates/model/fixtures.link rename to resources/static/jquery/generate/templates/model/fixtures.link diff --git a/browserid/static/jquery/generate/templates/model/models.link b/resources/static/jquery/generate/templates/model/models.link similarity index 100% rename from browserid/static/jquery/generate/templates/model/models.link rename to resources/static/jquery/generate/templates/model/models.link diff --git a/browserid/static/jquery/generate/templates/model/test/qunit.link b/resources/static/jquery/generate/templates/model/test/qunit.link similarity index 100% rename from browserid/static/jquery/generate/templates/model/test/qunit.link rename to resources/static/jquery/generate/templates/model/test/qunit.link diff --git a/browserid/static/jquery/generate/templates/page.ejs b/resources/static/jquery/generate/templates/page.ejs similarity index 100% rename from browserid/static/jquery/generate/templates/page.ejs rename to resources/static/jquery/generate/templates/page.ejs diff --git a/browserid/static/jquery/generate/templates/plugin/(application_name).html.ejs b/resources/static/jquery/generate/templates/plugin/(application_name).html.ejs similarity index 100% rename from browserid/static/jquery/generate/templates/plugin/(application_name).html.ejs rename to resources/static/jquery/generate/templates/plugin/(application_name).html.ejs diff --git a/browserid/static/jquery/generate/templates/plugin/(application_name).js.ejs b/resources/static/jquery/generate/templates/plugin/(application_name).js.ejs similarity index 100% rename from browserid/static/jquery/generate/templates/plugin/(application_name).js.ejs rename to resources/static/jquery/generate/templates/plugin/(application_name).js.ejs diff --git a/browserid/static/jquery/generate/templates/plugin/docs/.gitignore b/resources/static/jquery/generate/templates/plugin/docs/.gitignore similarity index 100% rename from browserid/static/jquery/generate/templates/plugin/docs/.gitignore rename to resources/static/jquery/generate/templates/plugin/docs/.gitignore diff --git a/browserid/static/jquery/generate/templates/plugin/fixtures/.ignore b/resources/static/jquery/generate/templates/plugin/fixtures/.ignore similarity index 100% rename from browserid/static/jquery/generate/templates/plugin/fixtures/.ignore rename to resources/static/jquery/generate/templates/plugin/fixtures/.ignore diff --git a/browserid/static/jquery/generate/templates/plugin/funcunit.html.ejs b/resources/static/jquery/generate/templates/plugin/funcunit.html.ejs similarity index 100% rename from browserid/static/jquery/generate/templates/plugin/funcunit.html.ejs rename to resources/static/jquery/generate/templates/plugin/funcunit.html.ejs diff --git a/browserid/static/jquery/generate/templates/plugin/qunit.html.ejs b/resources/static/jquery/generate/templates/plugin/qunit.html.ejs similarity index 100% rename from browserid/static/jquery/generate/templates/plugin/qunit.html.ejs rename to resources/static/jquery/generate/templates/plugin/qunit.html.ejs diff --git a/browserid/static/jquery/generate/templates/plugin/resources/.ignore b/resources/static/jquery/generate/templates/plugin/resources/.ignore similarity index 100% rename from browserid/static/jquery/generate/templates/plugin/resources/.ignore rename to resources/static/jquery/generate/templates/plugin/resources/.ignore diff --git a/browserid/static/jquery/generate/templates/plugin/scripts.link b/resources/static/jquery/generate/templates/plugin/scripts.link similarity index 100% rename from browserid/static/jquery/generate/templates/plugin/scripts.link rename to resources/static/jquery/generate/templates/plugin/scripts.link diff --git a/browserid/static/jquery/generate/templates/plugin/test.link b/resources/static/jquery/generate/templates/plugin/test.link similarity index 100% rename from browserid/static/jquery/generate/templates/plugin/test.link rename to resources/static/jquery/generate/templates/plugin/test.link diff --git a/browserid/static/jquery/generate/templates/plugin/views/.ignore b/resources/static/jquery/generate/templates/plugin/views/.ignore similarity index 100% rename from browserid/static/jquery/generate/templates/plugin/views/.ignore rename to resources/static/jquery/generate/templates/plugin/views/.ignore diff --git a/browserid/static/jquery/generate/templates/scaffold/controllers/(underscore)_controller.js.ejs b/resources/static/jquery/generate/templates/scaffold/controllers/(underscore)_controller.js.ejs similarity index 100% rename from browserid/static/jquery/generate/templates/scaffold/controllers/(underscore)_controller.js.ejs rename to resources/static/jquery/generate/templates/scaffold/controllers/(underscore)_controller.js.ejs diff --git a/browserid/static/jquery/generate/templates/scaffold/fixtures/(plural).json.get.ejs b/resources/static/jquery/generate/templates/scaffold/fixtures/(plural).json.get.ejs similarity index 100% rename from browserid/static/jquery/generate/templates/scaffold/fixtures/(plural).json.get.ejs rename to resources/static/jquery/generate/templates/scaffold/fixtures/(plural).json.get.ejs diff --git a/browserid/static/jquery/generate/templates/scaffold/models/(underscore).js.ejs b/resources/static/jquery/generate/templates/scaffold/models/(underscore).js.ejs similarity index 100% rename from browserid/static/jquery/generate/templates/scaffold/models/(underscore).js.ejs rename to resources/static/jquery/generate/templates/scaffold/models/(underscore).js.ejs diff --git a/browserid/static/jquery/generate/templates/scaffold/test/funcunit/(underscore)_controller_test.js.ejs b/resources/static/jquery/generate/templates/scaffold/test/funcunit/(underscore)_controller_test.js.ejs similarity index 100% rename from browserid/static/jquery/generate/templates/scaffold/test/funcunit/(underscore)_controller_test.js.ejs rename to resources/static/jquery/generate/templates/scaffold/test/funcunit/(underscore)_controller_test.js.ejs diff --git a/browserid/static/jquery/generate/templates/scaffold/test/qunit/(underscore)_test.js.ejs b/resources/static/jquery/generate/templates/scaffold/test/qunit/(underscore)_test.js.ejs similarity index 100% rename from browserid/static/jquery/generate/templates/scaffold/test/qunit/(underscore)_test.js.ejs rename to resources/static/jquery/generate/templates/scaffold/test/qunit/(underscore)_test.js.ejs diff --git a/browserid/static/jquery/generate/templates/scaffold/views/(underscore)/edit.ejs.ejs b/resources/static/jquery/generate/templates/scaffold/views/(underscore)/edit.ejs.ejs similarity index 100% rename from browserid/static/jquery/generate/templates/scaffold/views/(underscore)/edit.ejs.ejs rename to resources/static/jquery/generate/templates/scaffold/views/(underscore)/edit.ejs.ejs diff --git a/browserid/static/jquery/generate/templates/scaffold/views/(underscore)/init.ejs.ejs b/resources/static/jquery/generate/templates/scaffold/views/(underscore)/init.ejs.ejs similarity index 100% rename from browserid/static/jquery/generate/templates/scaffold/views/(underscore)/init.ejs.ejs rename to resources/static/jquery/generate/templates/scaffold/views/(underscore)/init.ejs.ejs diff --git a/browserid/static/jquery/generate/templates/scaffold/views/(underscore)/list.ejs.ejs b/resources/static/jquery/generate/templates/scaffold/views/(underscore)/list.ejs.ejs similarity index 100% rename from browserid/static/jquery/generate/templates/scaffold/views/(underscore)/list.ejs.ejs rename to resources/static/jquery/generate/templates/scaffold/views/(underscore)/list.ejs.ejs diff --git a/browserid/static/jquery/generate/templates/scaffold/views/(underscore)/show.ejs.ejs b/resources/static/jquery/generate/templates/scaffold/views/(underscore)/show.ejs.ejs similarity index 100% rename from browserid/static/jquery/generate/templates/scaffold/views/(underscore)/show.ejs.ejs rename to resources/static/jquery/generate/templates/scaffold/views/(underscore)/show.ejs.ejs diff --git a/browserid/static/jquery/generate/test/app_plugin_model_controller.js b/resources/static/jquery/generate/test/app_plugin_model_controller.js similarity index 100% rename from browserid/static/jquery/generate/test/app_plugin_model_controller.js rename to resources/static/jquery/generate/test/app_plugin_model_controller.js diff --git a/browserid/static/jquery/generate/test/run.js b/resources/static/jquery/generate/test/run.js similarity index 100% rename from browserid/static/jquery/generate/test/run.js rename to resources/static/jquery/generate/test/run.js diff --git a/browserid/static/jquery/generate/test/scaffold.js b/resources/static/jquery/generate/test/scaffold.js similarity index 100% rename from browserid/static/jquery/generate/test/scaffold.js rename to resources/static/jquery/generate/test/scaffold.js diff --git a/browserid/static/jquery/jquery.js b/resources/static/jquery/jquery.js similarity index 100% rename from browserid/static/jquery/jquery.js rename to resources/static/jquery/jquery.js diff --git a/browserid/static/jquery/lang/deparam/deparam.js b/resources/static/jquery/lang/deparam/deparam.js similarity index 100% rename from browserid/static/jquery/lang/deparam/deparam.js rename to resources/static/jquery/lang/deparam/deparam.js diff --git a/browserid/static/jquery/lang/deparam/deparam_test.js b/resources/static/jquery/lang/deparam/deparam_test.js similarity index 100% rename from browserid/static/jquery/lang/deparam/deparam_test.js rename to resources/static/jquery/lang/deparam/deparam_test.js diff --git a/browserid/static/jquery/lang/deparam/qunit.html b/resources/static/jquery/lang/deparam/qunit.html similarity index 100% rename from browserid/static/jquery/lang/deparam/qunit.html rename to resources/static/jquery/lang/deparam/qunit.html diff --git a/browserid/static/jquery/lang/json/json.js b/resources/static/jquery/lang/json/json.js similarity index 100% rename from browserid/static/jquery/lang/json/json.js rename to resources/static/jquery/lang/json/json.js diff --git a/browserid/static/jquery/lang/lang.html b/resources/static/jquery/lang/lang.html similarity index 100% rename from browserid/static/jquery/lang/lang.html rename to resources/static/jquery/lang/lang.html diff --git a/browserid/static/jquery/lang/lang.js b/resources/static/jquery/lang/lang.js similarity index 100% rename from browserid/static/jquery/lang/lang.js rename to resources/static/jquery/lang/lang.js diff --git a/browserid/static/jquery/lang/lang_test.js b/resources/static/jquery/lang/lang_test.js similarity index 100% rename from browserid/static/jquery/lang/lang_test.js rename to resources/static/jquery/lang/lang_test.js diff --git a/browserid/static/jquery/lang/openajax/openajax.html b/resources/static/jquery/lang/openajax/openajax.html similarity index 100% rename from browserid/static/jquery/lang/openajax/openajax.html rename to resources/static/jquery/lang/openajax/openajax.html diff --git a/browserid/static/jquery/lang/openajax/openajax.js b/resources/static/jquery/lang/openajax/openajax.js similarity index 100% rename from browserid/static/jquery/lang/openajax/openajax.js rename to resources/static/jquery/lang/openajax/openajax.js diff --git a/browserid/static/jquery/lang/qunit.html b/resources/static/jquery/lang/qunit.html similarity index 100% rename from browserid/static/jquery/lang/qunit.html rename to resources/static/jquery/lang/qunit.html diff --git a/browserid/static/jquery/lang/rsplit/rsplit.js b/resources/static/jquery/lang/rsplit/rsplit.js similarity index 100% rename from browserid/static/jquery/lang/rsplit/rsplit.js rename to resources/static/jquery/lang/rsplit/rsplit.js diff --git a/browserid/static/jquery/lang/vector/vector.js b/resources/static/jquery/lang/vector/vector.js similarity index 100% rename from browserid/static/jquery/lang/vector/vector.js rename to resources/static/jquery/lang/vector/vector.js diff --git a/browserid/static/jquery/model/associations/associations.html b/resources/static/jquery/model/associations/associations.html similarity index 100% rename from browserid/static/jquery/model/associations/associations.html rename to resources/static/jquery/model/associations/associations.html diff --git a/browserid/static/jquery/model/associations/associations.js b/resources/static/jquery/model/associations/associations.js similarity index 100% rename from browserid/static/jquery/model/associations/associations.js rename to resources/static/jquery/model/associations/associations.js diff --git a/browserid/static/jquery/model/associations/qunit.html b/resources/static/jquery/model/associations/qunit.html similarity index 100% rename from browserid/static/jquery/model/associations/qunit.html rename to resources/static/jquery/model/associations/qunit.html diff --git a/browserid/static/jquery/model/associations/test/qunit/associations_test.js b/resources/static/jquery/model/associations/test/qunit/associations_test.js similarity index 100% rename from browserid/static/jquery/model/associations/test/qunit/associations_test.js rename to resources/static/jquery/model/associations/test/qunit/associations_test.js diff --git a/browserid/static/jquery/model/associations/test/qunit/qunit.js b/resources/static/jquery/model/associations/test/qunit/qunit.js similarity index 100% rename from browserid/static/jquery/model/associations/test/qunit/qunit.js rename to resources/static/jquery/model/associations/test/qunit/qunit.js diff --git a/browserid/static/jquery/model/backup/backup.html b/resources/static/jquery/model/backup/backup.html similarity index 100% rename from browserid/static/jquery/model/backup/backup.html rename to resources/static/jquery/model/backup/backup.html diff --git a/browserid/static/jquery/model/backup/backup.js b/resources/static/jquery/model/backup/backup.js similarity index 100% rename from browserid/static/jquery/model/backup/backup.js rename to resources/static/jquery/model/backup/backup.js diff --git a/browserid/static/jquery/model/backup/qunit.html b/resources/static/jquery/model/backup/qunit.html similarity index 100% rename from browserid/static/jquery/model/backup/qunit.html rename to resources/static/jquery/model/backup/qunit.html diff --git a/browserid/static/jquery/model/backup/qunit/qunit.js b/resources/static/jquery/model/backup/qunit/qunit.js similarity index 100% rename from browserid/static/jquery/model/backup/qunit/qunit.js rename to resources/static/jquery/model/backup/qunit/qunit.js diff --git a/browserid/static/jquery/model/demo-convert.html b/resources/static/jquery/model/demo-convert.html similarity index 100% rename from browserid/static/jquery/model/demo-convert.html rename to resources/static/jquery/model/demo-convert.html diff --git a/browserid/static/jquery/model/demo-dom.html b/resources/static/jquery/model/demo-dom.html similarity index 100% rename from browserid/static/jquery/model/demo-dom.html rename to resources/static/jquery/model/demo-dom.html diff --git a/browserid/static/jquery/model/demo-encapsulate.html b/resources/static/jquery/model/demo-encapsulate.html similarity index 100% rename from browserid/static/jquery/model/demo-encapsulate.html rename to resources/static/jquery/model/demo-encapsulate.html diff --git a/browserid/static/jquery/model/demo-events.html b/resources/static/jquery/model/demo-events.html similarity index 100% rename from browserid/static/jquery/model/demo-events.html rename to resources/static/jquery/model/demo-events.html diff --git a/browserid/static/jquery/model/demo-setter.html b/resources/static/jquery/model/demo-setter.html similarity index 100% rename from browserid/static/jquery/model/demo-setter.html rename to resources/static/jquery/model/demo-setter.html diff --git a/browserid/static/jquery/model/fixtures/school.json b/resources/static/jquery/model/fixtures/school.json similarity index 100% rename from browserid/static/jquery/model/fixtures/school.json rename to resources/static/jquery/model/fixtures/school.json diff --git a/browserid/static/jquery/model/fixtures/schools.json b/resources/static/jquery/model/fixtures/schools.json similarity index 100% rename from browserid/static/jquery/model/fixtures/schools.json rename to resources/static/jquery/model/fixtures/schools.json diff --git a/browserid/static/jquery/model/guesstype/guesstype.js b/resources/static/jquery/model/guesstype/guesstype.js similarity index 100% rename from browserid/static/jquery/model/guesstype/guesstype.js rename to resources/static/jquery/model/guesstype/guesstype.js diff --git a/browserid/static/jquery/model/guesstype/guesstype_test.js b/resources/static/jquery/model/guesstype/guesstype_test.js similarity index 100% rename from browserid/static/jquery/model/guesstype/guesstype_test.js rename to resources/static/jquery/model/guesstype/guesstype_test.js diff --git a/browserid/static/jquery/model/list/cookie/cookie.html b/resources/static/jquery/model/list/cookie/cookie.html similarity index 100% rename from browserid/static/jquery/model/list/cookie/cookie.html rename to resources/static/jquery/model/list/cookie/cookie.html diff --git a/browserid/static/jquery/model/list/cookie/cookie.js b/resources/static/jquery/model/list/cookie/cookie.js similarity index 100% rename from browserid/static/jquery/model/list/cookie/cookie.js rename to resources/static/jquery/model/list/cookie/cookie.js diff --git a/browserid/static/jquery/model/list/cookie/qunit.html b/resources/static/jquery/model/list/cookie/qunit.html similarity index 100% rename from browserid/static/jquery/model/list/cookie/qunit.html rename to resources/static/jquery/model/list/cookie/qunit.html diff --git a/browserid/static/jquery/model/list/cookie/qunit/qunit.js b/resources/static/jquery/model/list/cookie/qunit/qunit.js similarity index 100% rename from browserid/static/jquery/model/list/cookie/qunit/qunit.js rename to resources/static/jquery/model/list/cookie/qunit/qunit.js diff --git a/browserid/static/jquery/model/list/list-insert.html b/resources/static/jquery/model/list/list-insert.html similarity index 100% rename from browserid/static/jquery/model/list/list-insert.html rename to resources/static/jquery/model/list/list-insert.html diff --git a/browserid/static/jquery/model/list/list.html b/resources/static/jquery/model/list/list.html similarity index 100% rename from browserid/static/jquery/model/list/list.html rename to resources/static/jquery/model/list/list.html diff --git a/browserid/static/jquery/model/list/list.js b/resources/static/jquery/model/list/list.js similarity index 100% rename from browserid/static/jquery/model/list/list.js rename to resources/static/jquery/model/list/list.js diff --git a/browserid/static/jquery/model/list/local/local.js b/resources/static/jquery/model/list/local/local.js similarity index 100% rename from browserid/static/jquery/model/list/local/local.js rename to resources/static/jquery/model/list/local/local.js diff --git a/browserid/static/jquery/model/list/memory.html b/resources/static/jquery/model/list/memory.html similarity index 100% rename from browserid/static/jquery/model/list/memory.html rename to resources/static/jquery/model/list/memory.html diff --git a/browserid/static/jquery/model/list/qunit.html b/resources/static/jquery/model/list/qunit.html similarity index 100% rename from browserid/static/jquery/model/list/qunit.html rename to resources/static/jquery/model/list/qunit.html diff --git a/browserid/static/jquery/model/list/test/qunit/list_test.js b/resources/static/jquery/model/list/test/qunit/list_test.js similarity index 100% rename from browserid/static/jquery/model/list/test/qunit/list_test.js rename to resources/static/jquery/model/list/test/qunit/list_test.js diff --git a/browserid/static/jquery/model/list/test/qunit/qunit.js b/resources/static/jquery/model/list/test/qunit/qunit.js similarity index 100% rename from browserid/static/jquery/model/list/test/qunit/qunit.js rename to resources/static/jquery/model/list/test/qunit/qunit.js diff --git a/browserid/static/jquery/model/model.js b/resources/static/jquery/model/model.js similarity index 100% rename from browserid/static/jquery/model/model.js rename to resources/static/jquery/model/model.js diff --git a/browserid/static/jquery/model/modelBinder.html b/resources/static/jquery/model/modelBinder.html similarity index 100% rename from browserid/static/jquery/model/modelBinder.html rename to resources/static/jquery/model/modelBinder.html diff --git a/browserid/static/jquery/model/pages/deferreds.js b/resources/static/jquery/model/pages/deferreds.js similarity index 100% rename from browserid/static/jquery/model/pages/deferreds.js rename to resources/static/jquery/model/pages/deferreds.js diff --git a/browserid/static/jquery/model/pages/encapsulate.js b/resources/static/jquery/model/pages/encapsulate.js similarity index 100% rename from browserid/static/jquery/model/pages/encapsulate.js rename to resources/static/jquery/model/pages/encapsulate.js diff --git a/browserid/static/jquery/model/pages/events.js b/resources/static/jquery/model/pages/events.js similarity index 100% rename from browserid/static/jquery/model/pages/events.js rename to resources/static/jquery/model/pages/events.js diff --git a/browserid/static/jquery/model/pages/typeconversion.js b/resources/static/jquery/model/pages/typeconversion.js similarity index 100% rename from browserid/static/jquery/model/pages/typeconversion.js rename to resources/static/jquery/model/pages/typeconversion.js diff --git a/browserid/static/jquery/model/qunit.html b/resources/static/jquery/model/qunit.html similarity index 100% rename from browserid/static/jquery/model/qunit.html rename to resources/static/jquery/model/qunit.html diff --git a/browserid/static/jquery/model/service/json_rest/json_rest.js b/resources/static/jquery/model/service/json_rest/json_rest.js similarity index 100% rename from browserid/static/jquery/model/service/json_rest/json_rest.js rename to resources/static/jquery/model/service/json_rest/json_rest.js diff --git a/browserid/static/jquery/model/service/service.js b/resources/static/jquery/model/service/service.js similarity index 100% rename from browserid/static/jquery/model/service/service.js rename to resources/static/jquery/model/service/service.js diff --git a/browserid/static/jquery/model/service/twitter/twitter.html b/resources/static/jquery/model/service/twitter/twitter.html similarity index 100% rename from browserid/static/jquery/model/service/twitter/twitter.html rename to resources/static/jquery/model/service/twitter/twitter.html diff --git a/browserid/static/jquery/model/service/twitter/twitter.js b/resources/static/jquery/model/service/twitter/twitter.js similarity index 100% rename from browserid/static/jquery/model/service/twitter/twitter.js rename to resources/static/jquery/model/service/twitter/twitter.js diff --git a/browserid/static/jquery/model/service/yql/yql.html b/resources/static/jquery/model/service/yql/yql.html similarity index 100% rename from browserid/static/jquery/model/service/yql/yql.html rename to resources/static/jquery/model/service/yql/yql.html diff --git a/browserid/static/jquery/model/service/yql/yql.js b/resources/static/jquery/model/service/yql/yql.js similarity index 100% rename from browserid/static/jquery/model/service/yql/yql.js rename to resources/static/jquery/model/service/yql/yql.js diff --git a/browserid/static/jquery/model/test/4.json b/resources/static/jquery/model/test/4.json similarity index 100% rename from browserid/static/jquery/model/test/4.json rename to resources/static/jquery/model/test/4.json diff --git a/browserid/static/jquery/model/test/create.json b/resources/static/jquery/model/test/create.json similarity index 100% rename from browserid/static/jquery/model/test/create.json rename to resources/static/jquery/model/test/create.json diff --git a/browserid/static/jquery/model/test/people.json b/resources/static/jquery/model/test/people.json similarity index 100% rename from browserid/static/jquery/model/test/people.json rename to resources/static/jquery/model/test/people.json diff --git a/browserid/static/jquery/model/test/person.json b/resources/static/jquery/model/test/person.json similarity index 100% rename from browserid/static/jquery/model/test/person.json rename to resources/static/jquery/model/test/person.json diff --git a/browserid/static/jquery/model/test/qunit/findAll.json b/resources/static/jquery/model/test/qunit/findAll.json similarity index 100% rename from browserid/static/jquery/model/test/qunit/findAll.json rename to resources/static/jquery/model/test/qunit/findAll.json diff --git a/browserid/static/jquery/model/test/qunit/model_test.js b/resources/static/jquery/model/test/qunit/model_test.js similarity index 100% rename from browserid/static/jquery/model/test/qunit/model_test.js rename to resources/static/jquery/model/test/qunit/model_test.js diff --git a/browserid/static/jquery/model/test/qunit/qunit.js b/resources/static/jquery/model/test/qunit/qunit.js similarity index 100% rename from browserid/static/jquery/model/test/qunit/qunit.js rename to resources/static/jquery/model/test/qunit/qunit.js diff --git a/browserid/static/jquery/model/test/schools.json b/resources/static/jquery/model/test/schools.json similarity index 100% rename from browserid/static/jquery/model/test/schools.json rename to resources/static/jquery/model/test/schools.json diff --git a/browserid/static/jquery/model/test/update4.json b/resources/static/jquery/model/test/update4.json similarity index 100% rename from browserid/static/jquery/model/test/update4.json rename to resources/static/jquery/model/test/update4.json diff --git a/browserid/static/jquery/model/validations/qunit.html b/resources/static/jquery/model/validations/qunit.html similarity index 100% rename from browserid/static/jquery/model/validations/qunit.html rename to resources/static/jquery/model/validations/qunit.html diff --git a/browserid/static/jquery/model/validations/qunit/validations_test.js b/resources/static/jquery/model/validations/qunit/validations_test.js similarity index 100% rename from browserid/static/jquery/model/validations/qunit/validations_test.js rename to resources/static/jquery/model/validations/qunit/validations_test.js diff --git a/browserid/static/jquery/model/validations/validations.html b/resources/static/jquery/model/validations/validations.html similarity index 100% rename from browserid/static/jquery/model/validations/validations.html rename to resources/static/jquery/model/validations/validations.html diff --git a/browserid/static/jquery/model/validations/validations.js b/resources/static/jquery/model/validations/validations.js similarity index 100% rename from browserid/static/jquery/model/validations/validations.js rename to resources/static/jquery/model/validations/validations.js diff --git a/browserid/static/jquery/qunit.html b/resources/static/jquery/qunit.html similarity index 100% rename from browserid/static/jquery/qunit.html rename to resources/static/jquery/qunit.html diff --git a/browserid/static/jquery/test/qunit/integration.js b/resources/static/jquery/test/qunit/integration.js similarity index 100% rename from browserid/static/jquery/test/qunit/integration.js rename to resources/static/jquery/test/qunit/integration.js diff --git a/browserid/static/jquery/test/qunit/qunit.js b/resources/static/jquery/test/qunit/qunit.js similarity index 100% rename from browserid/static/jquery/test/qunit/qunit.js rename to resources/static/jquery/test/qunit/qunit.js diff --git a/browserid/static/jquery/test/run.js b/resources/static/jquery/test/run.js similarity index 100% rename from browserid/static/jquery/test/run.js rename to resources/static/jquery/test/run.js diff --git a/browserid/static/jquery/test/template.ejs b/resources/static/jquery/test/template.ejs similarity index 100% rename from browserid/static/jquery/test/template.ejs rename to resources/static/jquery/test/template.ejs diff --git a/browserid/static/jquery/test/thing.json b/resources/static/jquery/test/thing.json similarity index 100% rename from browserid/static/jquery/test/thing.json rename to resources/static/jquery/test/thing.json diff --git a/browserid/static/jquery/tie/qunit.html b/resources/static/jquery/tie/qunit.html similarity index 100% rename from browserid/static/jquery/tie/qunit.html rename to resources/static/jquery/tie/qunit.html diff --git a/browserid/static/jquery/tie/tie.html b/resources/static/jquery/tie/tie.html similarity index 100% rename from browserid/static/jquery/tie/tie.html rename to resources/static/jquery/tie/tie.html diff --git a/browserid/static/jquery/tie/tie.js b/resources/static/jquery/tie/tie.js similarity index 100% rename from browserid/static/jquery/tie/tie.js rename to resources/static/jquery/tie/tie.js diff --git a/browserid/static/jquery/tie/tie_test.js b/resources/static/jquery/tie/tie_test.js similarity index 100% rename from browserid/static/jquery/tie/tie_test.js rename to resources/static/jquery/tie/tie_test.js diff --git a/browserid/static/jquery/update b/resources/static/jquery/update similarity index 100% rename from browserid/static/jquery/update rename to resources/static/jquery/update diff --git a/browserid/static/jquery/view/compress.js b/resources/static/jquery/view/compress.js similarity index 100% rename from browserid/static/jquery/view/compress.js rename to resources/static/jquery/view/compress.js diff --git a/browserid/static/jquery/view/ejs/ejs.html b/resources/static/jquery/view/ejs/ejs.html similarity index 100% rename from browserid/static/jquery/view/ejs/ejs.html rename to resources/static/jquery/view/ejs/ejs.html diff --git a/browserid/static/jquery/view/ejs/ejs.js b/resources/static/jquery/view/ejs/ejs.js similarity index 100% rename from browserid/static/jquery/view/ejs/ejs.js rename to resources/static/jquery/view/ejs/ejs.js diff --git a/browserid/static/jquery/view/ejs/funcunit.html b/resources/static/jquery/view/ejs/funcunit.html similarity index 100% rename from browserid/static/jquery/view/ejs/funcunit.html rename to resources/static/jquery/view/ejs/funcunit.html diff --git a/browserid/static/jquery/view/ejs/other.js b/resources/static/jquery/view/ejs/other.js similarity index 100% rename from browserid/static/jquery/view/ejs/other.js rename to resources/static/jquery/view/ejs/other.js diff --git a/browserid/static/jquery/view/ejs/qunit.html b/resources/static/jquery/view/ejs/qunit.html similarity index 100% rename from browserid/static/jquery/view/ejs/qunit.html rename to resources/static/jquery/view/ejs/qunit.html diff --git a/browserid/static/jquery/view/ejs/test/qunit/ejs_test.js b/resources/static/jquery/view/ejs/test/qunit/ejs_test.js similarity index 100% rename from browserid/static/jquery/view/ejs/test/qunit/ejs_test.js rename to resources/static/jquery/view/ejs/test/qunit/ejs_test.js diff --git a/browserid/static/jquery/view/ejs/test/qunit/qunit.js b/resources/static/jquery/view/ejs/test/qunit/qunit.js similarity index 100% rename from browserid/static/jquery/view/ejs/test/qunit/qunit.js rename to resources/static/jquery/view/ejs/test/qunit/qunit.js diff --git a/browserid/static/jquery/view/fulljslint.js b/resources/static/jquery/view/fulljslint.js similarity index 100% rename from browserid/static/jquery/view/fulljslint.js rename to resources/static/jquery/view/fulljslint.js diff --git a/browserid/static/jquery/view/helpers/helpers.js b/resources/static/jquery/view/helpers/helpers.js similarity index 100% rename from browserid/static/jquery/view/helpers/helpers.js rename to resources/static/jquery/view/helpers/helpers.js diff --git a/browserid/static/jquery/view/jaml/jaml.js b/resources/static/jquery/view/jaml/jaml.js similarity index 100% rename from browserid/static/jquery/view/jaml/jaml.js rename to resources/static/jquery/view/jaml/jaml.js diff --git a/browserid/static/jquery/view/micro/micro.js b/resources/static/jquery/view/micro/micro.js similarity index 100% rename from browserid/static/jquery/view/micro/micro.js rename to resources/static/jquery/view/micro/micro.js diff --git a/browserid/static/jquery/view/qunit.html b/resources/static/jquery/view/qunit.html similarity index 100% rename from browserid/static/jquery/view/qunit.html rename to resources/static/jquery/view/qunit.html diff --git a/browserid/static/jquery/view/test/compression/compression.html b/resources/static/jquery/view/test/compression/compression.html similarity index 100% rename from browserid/static/jquery/view/test/compression/compression.html rename to resources/static/jquery/view/test/compression/compression.html diff --git a/browserid/static/jquery/view/test/compression/compression.js b/resources/static/jquery/view/test/compression/compression.js similarity index 100% rename from browserid/static/jquery/view/test/compression/compression.js rename to resources/static/jquery/view/test/compression/compression.js diff --git a/browserid/static/jquery/view/test/compression/production.js b/resources/static/jquery/view/test/compression/production.js similarity index 100% rename from browserid/static/jquery/view/test/compression/production.js rename to resources/static/jquery/view/test/compression/production.js diff --git a/browserid/static/jquery/view/test/compression/run.js b/resources/static/jquery/view/test/compression/run.js similarity index 100% rename from browserid/static/jquery/view/test/compression/run.js rename to resources/static/jquery/view/test/compression/run.js diff --git a/browserid/static/jquery/view/test/compression/views/keep.me b/resources/static/jquery/view/test/compression/views/keep.me similarity index 100% rename from browserid/static/jquery/view/test/compression/views/keep.me rename to resources/static/jquery/view/test/compression/views/keep.me diff --git a/browserid/static/jquery/view/test/qunit/deferred.ejs b/resources/static/jquery/view/test/qunit/deferred.ejs similarity index 100% rename from browserid/static/jquery/view/test/qunit/deferred.ejs rename to resources/static/jquery/view/test/qunit/deferred.ejs diff --git a/browserid/static/jquery/view/test/qunit/deferreds.ejs b/resources/static/jquery/view/test/qunit/deferreds.ejs similarity index 100% rename from browserid/static/jquery/view/test/qunit/deferreds.ejs rename to resources/static/jquery/view/test/qunit/deferreds.ejs diff --git a/browserid/static/jquery/view/test/qunit/hookup.ejs b/resources/static/jquery/view/test/qunit/hookup.ejs similarity index 100% rename from browserid/static/jquery/view/test/qunit/hookup.ejs rename to resources/static/jquery/view/test/qunit/hookup.ejs diff --git a/browserid/static/jquery/view/test/qunit/large.ejs b/resources/static/jquery/view/test/qunit/large.ejs similarity index 100% rename from browserid/static/jquery/view/test/qunit/large.ejs rename to resources/static/jquery/view/test/qunit/large.ejs diff --git a/browserid/static/jquery/view/test/qunit/nested_plugin.ejs b/resources/static/jquery/view/test/qunit/nested_plugin.ejs similarity index 100% rename from browserid/static/jquery/view/test/qunit/nested_plugin.ejs rename to resources/static/jquery/view/test/qunit/nested_plugin.ejs diff --git a/browserid/static/jquery/view/test/qunit/plugin.ejs b/resources/static/jquery/view/test/qunit/plugin.ejs similarity index 100% rename from browserid/static/jquery/view/test/qunit/plugin.ejs rename to resources/static/jquery/view/test/qunit/plugin.ejs diff --git a/browserid/static/jquery/view/test/qunit/qunit.js b/resources/static/jquery/view/test/qunit/qunit.js similarity index 100% rename from browserid/static/jquery/view/test/qunit/qunit.js rename to resources/static/jquery/view/test/qunit/qunit.js diff --git a/browserid/static/jquery/view/test/qunit/temp.ejs b/resources/static/jquery/view/test/qunit/temp.ejs similarity index 100% rename from browserid/static/jquery/view/test/qunit/temp.ejs rename to resources/static/jquery/view/test/qunit/temp.ejs diff --git a/browserid/static/jquery/view/test/qunit/template.ejs b/resources/static/jquery/view/test/qunit/template.ejs similarity index 100% rename from browserid/static/jquery/view/test/qunit/template.ejs rename to resources/static/jquery/view/test/qunit/template.ejs diff --git a/browserid/static/jquery/view/test/qunit/template.jaml b/resources/static/jquery/view/test/qunit/template.jaml similarity index 100% rename from browserid/static/jquery/view/test/qunit/template.jaml rename to resources/static/jquery/view/test/qunit/template.jaml diff --git a/browserid/static/jquery/view/test/qunit/template.micro b/resources/static/jquery/view/test/qunit/template.micro similarity index 100% rename from browserid/static/jquery/view/test/qunit/template.micro rename to resources/static/jquery/view/test/qunit/template.micro diff --git a/browserid/static/jquery/view/test/qunit/template.tmpl b/resources/static/jquery/view/test/qunit/template.tmpl similarity index 100% rename from browserid/static/jquery/view/test/qunit/template.tmpl rename to resources/static/jquery/view/test/qunit/template.tmpl diff --git a/browserid/static/jquery/view/test/qunit/view_test.js b/resources/static/jquery/view/test/qunit/view_test.js similarity index 100% rename from browserid/static/jquery/view/test/qunit/view_test.js rename to resources/static/jquery/view/test/qunit/view_test.js diff --git a/browserid/static/jquery/view/tmpl/test.tmpl b/resources/static/jquery/view/tmpl/test.tmpl similarity index 100% rename from browserid/static/jquery/view/tmpl/test.tmpl rename to resources/static/jquery/view/tmpl/test.tmpl diff --git a/browserid/static/jquery/view/tmpl/tmpl.js b/resources/static/jquery/view/tmpl/tmpl.js similarity index 100% rename from browserid/static/jquery/view/tmpl/tmpl.js rename to resources/static/jquery/view/tmpl/tmpl.js diff --git a/browserid/static/jquery/view/tmpl/tmpl_test.js b/resources/static/jquery/view/tmpl/tmpl_test.js similarity index 100% rename from browserid/static/jquery/view/tmpl/tmpl_test.js rename to resources/static/jquery/view/tmpl/tmpl_test.js diff --git a/browserid/static/jquery/view/view.html b/resources/static/jquery/view/view.html similarity index 100% rename from browserid/static/jquery/view/view.html rename to resources/static/jquery/view/view.html diff --git a/browserid/static/jquery/view/view.js b/resources/static/jquery/view/view.js similarity index 100% rename from browserid/static/jquery/view/view.js rename to resources/static/jquery/view/view.js diff --git a/browserid/static/js.bat b/resources/static/js.bat similarity index 100% rename from browserid/static/js.bat rename to resources/static/js.bat diff --git a/browserid/static/js/browserid.js b/resources/static/js/browserid.js similarity index 77% rename from browserid/static/js/browserid.js rename to resources/static/js/browserid.js index 7ed223b97bc95ec1492183dba38ccddfda3c44c9..0e698186572e127be8725629db0067c3c0dcfa1d 100644 --- a/browserid/static/js/browserid.js +++ b/resources/static/js/browserid.js @@ -34,8 +34,6 @@ * * ***** END LICENSE BLOCK ***** */ -window.BrowserID = window.BrowserID || {}; - $(function() { "use strict"; @@ -43,21 +41,11 @@ $(function() { * For the main page */ - function getParameterByName( name ) { - name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]"); - var regexS = "[\\?&]"+name+"=([^&#]*)"; - var regex = new RegExp( regexS ); - var results = regex.exec( window.location.href ); - if( results == null ) - return ""; - else - return decodeURIComponent(results[1].replace(/\+/g, " ")); - } - - var token = getParameterByName("token"), - path = document.location.pathname, - bid = BrowserID, - user = bid.User; + var bid = BrowserID, + pageHelpers = bid.PageHelpers, + user = bid.User, + token = pageHelpers.getParameterByName("token"), + path = document.location.pathname; if (!path || path === "/") { bid.index(); @@ -90,21 +78,20 @@ $(function() { }); }); + $(".display_always,.display_auth,.display_nonauth").hide(); + + var ANIMATION_TIME = 500; user.checkAuthentication(function(authenticated) { + $(".display_always").fadeIn(ANIMATION_TIME); + if (authenticated) { - $("#content").fadeIn("slow"); + $(".display_auth").fadeIn(ANIMATION_TIME); if ($('#emailList').length) { bid.manageAccount(); } } else { - // If vAlign exists (main page), it takes precedence over content. - if( $("#vAlign").length) { - $("#vAlign").fadeIn("slow"); - } - else { - $("#content").fadeIn("slow"); - } + $(".display_nonauth").fadeIn(ANIMATION_TIME); } }); diff --git a/browserid/static/js/highlight.js b/resources/static/js/highlight.js similarity index 100% rename from browserid/static/js/highlight.js rename to resources/static/js/highlight.js diff --git a/browserid/static/js/html5shim.js b/resources/static/js/html5shim.js similarity index 100% rename from browserid/static/js/html5shim.js rename to resources/static/js/html5shim.js diff --git a/browserid/static/js/jquery-1.6.2.min.js b/resources/static/js/jquery-1.6.2.min.js similarity index 100% rename from browserid/static/js/jquery-1.6.2.min.js rename to resources/static/js/jquery-1.6.2.min.js diff --git a/browserid/static/js/json2.js b/resources/static/js/json2.js similarity index 100% rename from browserid/static/js/json2.js rename to resources/static/js/json2.js diff --git a/resources/static/js/page_helpers.js b/resources/static/js/page_helpers.js new file mode 100644 index 0000000000000000000000000000000000000000..1e0bb323885460864dbb5ddd2582ff1504de1ccc --- /dev/null +++ b/resources/static/js/page_helpers.js @@ -0,0 +1,93 @@ +/*globals BrowserID: true, _: true */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla BrowserID. + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +BrowserID.PageHelpers = (function() { + "use strict"; + + var win = window, + locStorage = win.localStorage, + bid = BrowserID; + + function setStoredEmail(email) { + locStorage.signInEmail = email; + } + + function onEmailKeyUp(event) { + var email = $("#email").val(); + setStoredEmail(email); + } + + function prefillEmail() { + // If the user tried to sign in on the sign up page with an existing email, + // place that email in the email field, then focus the password. + var el = $("#email"), + email = locStorage.signInEmail; + + if (email) { + el.val(email); + if ($("#password").length) $("#password").focus(); + } + + el.keyup(onEmailKeyUp); + } + + function clearStoredEmail() { + locStorage.removeItem("signInEmail"); + } + + function getStoredEmail() { + return locStorage.signInEmail || ""; + } + + function getParameterByName( name ) { + name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]"); + var regexS = "[\\?&]"+name+"=([^&#]*)"; + var regex = new RegExp( regexS ); + var results = regex.exec( win.location.href ); + if( results === null ) + return ""; + else + return decodeURIComponent(results[1].replace(/\+/g, " ")); + } + + return { + setupEmail: prefillEmail, + setStoredEmail: setStoredEmail, + clearStoredEmail: clearStoredEmail, + getStoredEmail: getStoredEmail, + getParameterByName: getParameterByName + }; +}()); diff --git a/browserid/static/js/pages/add_email_address.js b/resources/static/js/pages/add_email_address.js similarity index 100% rename from browserid/static/js/pages/add_email_address.js rename to resources/static/js/pages/add_email_address.js diff --git a/resources/static/js/pages/forgot.js b/resources/static/js/pages/forgot.js new file mode 100644 index 0000000000000000000000000000000000000000..32004aef4ed8602904b56fd38a62f18ff2abf114 --- /dev/null +++ b/resources/static/js/pages/forgot.js @@ -0,0 +1,92 @@ +/*globals BrowserID: true, $:true */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla BrowserID. + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +BrowserID.forgot = (function() { + "use strict"; + + var bid = BrowserID, + user = bid.User, + pageHelpers = bid.PageHelpers, + tooltip = bid.Tooltip; + + function submit(event) { + if (event) event.preventDefault(); + + // GET RID OF THIS HIDE CRAP AND USE CSS! + $(".notifications .notification").hide(); + + var email = $("#email").val(), + valid = bid.Validation.email(email); + + if (valid) { + user.requestPasswordReset(email, function onSuccess(info) { + if (info.success) { + pageHelpers.clearStoredEmail(); + $('#sent_to_email').html(email); + $('#forminputs').fadeOut(); + $(".notifications .notification.emailsent").fadeIn(); + } + else { + var tooltipEl = info.reason === "throttle" ? "#could_not_add" : "#not_registered"; + tooltip.showTooltip(tooltipEl); + } + }, function onFailure() { + $(".notifications .notification.doh").fadeIn(); + }); + } + }; + + function init() { + $("form input[autofocus]").focus(); + + pageHelpers.setupEmail(); + + $("#signUpForm").bind("submit", submit); + } + + function reset() { + $("#signUpForm").unbind("submit", submit); + } + + + var forgot = init; + forgot.submit = submit; + forgot.reset = reset; + + return forgot; + +}()); + diff --git a/browserid/static/js/pages/index.js b/resources/static/js/pages/index.js similarity index 100% rename from browserid/static/js/pages/index.js rename to resources/static/js/pages/index.js diff --git a/browserid/static/js/pages/manage_account.js b/resources/static/js/pages/manage_account.js similarity index 100% rename from browserid/static/js/pages/manage_account.js rename to resources/static/js/pages/manage_account.js diff --git a/browserid/static/js/pages/signin.js b/resources/static/js/pages/signin.js similarity index 86% rename from browserid/static/js/pages/signin.js rename to resources/static/js/pages/signin.js index 1df6ec7b827cf0eba6174b7a1d2d9301a58dfee2..69590b274a4aa2b63db65589d50d8bbd775a6958 100644 --- a/browserid/static/js/pages/signin.js +++ b/resources/static/js/pages/signin.js @@ -39,23 +39,13 @@ var bid = BrowserID, user = bid.User, + pageHelpers = bid.PageHelpers, validation = bid.Validation; - function prefillEmail() { - // If the user tried to sign in on the sign up page with an existing email, - // place that email in the email field, then focus the password. - var email = window.localStorage.signInEmail; - if (email) { - $("#email").val(email); - window.localStorage.removeItem('signInEmail'); - $("#password").focus(); - } - } - bid.signIn = function () { $("form input[autofocus]").focus(); - prefillEmail(); + pageHelpers.setupEmail(); $("#signUpForm").bind("submit", function(event) { event.preventDefault(); @@ -68,6 +58,7 @@ if (valid) { user.authenticate(email, password, function onSuccess(authenticated) { if (authenticated) { + pageHelpers.clearStoredEmail(); document.location = "/"; } else { diff --git a/browserid/static/js/pages/signup.js b/resources/static/js/pages/signup.js similarity index 96% rename from browserid/static/js/pages/signup.js rename to resources/static/js/pages/signup.js index c60e3f01e88fb2145528ca7e67bf5dd254f2d04d..429fbccc66c0bfcb29a5eed5324101cee7ca9dcf 100644 --- a/browserid/static/js/pages/signup.js +++ b/resources/static/js/pages/signup.js @@ -39,10 +39,10 @@ var bid = BrowserID, user = bid.User, + pageHelpers = bid.PageHelpers, ANIMATION_SPEED = 250; bid.signUp = function () { - function replaceWithMessage(selector) { $('.forminputs').fadeOut(ANIMATION_SPEED, function() { $(selector).fadeIn(ANIMATION_SPEED); @@ -61,6 +61,8 @@ $(function () { $("form input[autofocus]").focus(); + pageHelpers.setupEmail(); + $("#email").bind("keyup", function(event) { if (event.which !== 13) { $(".notification").fadeOut(ANIMATION_SPEED); @@ -79,6 +81,7 @@ user.isEmailRegistered(email, function(registered) { if (!registered) { + pageHelpers.clearStoredEmail(); user.createUser(email, function onSuccess(keypair) { $('#sentToEmail').html(email); replaceWithMessage(".emailsent"); @@ -87,7 +90,6 @@ else { $('#registeredEmail').html(email); showNotice(".alreadyRegistered"); - window.localStorage.signInEmail = email; } }, onFailure); }); diff --git a/browserid/static/js/pages/verify_email_address.js b/resources/static/js/pages/verify_email_address.js similarity index 98% rename from browserid/static/js/pages/verify_email_address.js rename to resources/static/js/pages/verify_email_address.js index 07642d077dc1639d25939d9e7510d3f6abc8a578..6c97f534739ff52494eaab9f051991526c3891ac 100644 --- a/browserid/static/js/pages/verify_email_address.js +++ b/resources/static/js/pages/verify_email_address.js @@ -37,8 +37,7 @@ (function() { "use strict"; - var bid = BrowserID, - tooltip = bid.Tooltip; + var bid = BrowserID; function showError(el) { $(el).fadeIn(250); diff --git a/resources/static/relay/relay.js b/resources/static/relay/relay.js new file mode 100644 index 0000000000000000000000000000000000000000..d9c86f4c13d0b4122eb43610ae4662769ce1c06e --- /dev/null +++ b/resources/static/relay/relay.js @@ -0,0 +1,156 @@ +/*global Channel: true, errorOut: true */ + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla BrowserID. + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + + +(function() { + "use strict"; + + window.console = window.console || { + log: function() {} + }; + + BrowserID.Relay = (function() { + var transaction, + origin, + channel = Channel, + win = window, + registerCB; + + + function init(options) { + origin = transaction = registerCB = undefined; + + if(options.window) { + win = options.window; + } + + if(options.channel) { + channel = options.channel; + } + } + + function open() { + var rpc = channel.build({ + window: win, + origin: "*", + scope: "mozid" + }); + + rpc.bind("getVerifiedEmail", function(trans, s) { + trans.delayReturn(true); + origin = trans.origin; + transaction = trans; + + // If the client has run early and already registered its registration + // callback, call it now. + if (registerCB) { + registerCB(origin, completionCB); + } + }); + } + + function registerClient(callback) { + // If the origin is ready, call the callback immediately. + if (origin) { + callback(origin, completionCB); + } + else { + registerCB = callback; + } + } + + function errorOut(code) { + function getVerboseMessage(code) { + var msgs = { + "canceled": "user canceled selection", + "notImplemented": "the user tried to invoke behavior that's not yet implemented", + "serverError": "a technical problem was encountered while trying to communicate with BrowserID servers." + }; + var msg = msgs[code]; + if (!msg) { + alert("need verbose message for " + code); + msg = "unknown error"; + } + return msg; + } + transaction.error(code, getVerboseMessage(code)); + } + + /** + * The client calls this to relay a message back to the RP whenever it is + * complete. This function is passed to the client when the client does + * its registerClient. + */ + function completionCB(status, error) { + if(error) { + errorOut(error); + } + else { + try { + transaction.complete(status); + } catch(e) { + // The relay function is called a second time after the + // initial success, when the window is closing. + } + } + } + + + return { + /** + * Initialize the relay. + * @method init + * @param {object} [options] - options used to override window, channel + * for unit testing. + */ + init: init, + + /** + * Open the relay with the parent window. + * @method open + */ + open: open, + + /** + * Register a client to use the relay + * @method registerClient + */ + registerClient: registerClient + }; + }()); + +}()); diff --git a/browserid/static/steal/.gitignore b/resources/static/steal/.gitignore similarity index 100% rename from browserid/static/steal/.gitignore rename to resources/static/steal/.gitignore diff --git a/browserid/static/steal/README b/resources/static/steal/README similarity index 100% rename from browserid/static/steal/README rename to resources/static/steal/README diff --git a/browserid/static/steal/build/apps/apps.js b/resources/static/steal/build/apps/apps.js similarity index 100% rename from browserid/static/steal/build/apps/apps.js rename to resources/static/steal/build/apps/apps.js diff --git a/browserid/static/steal/build/apps/test.js b/resources/static/steal/build/apps/test.js similarity index 100% rename from browserid/static/steal/build/apps/test.js rename to resources/static/steal/build/apps/test.js diff --git a/browserid/static/steal/build/build.js b/resources/static/steal/build/build.js similarity index 100% rename from browserid/static/steal/build/build.js rename to resources/static/steal/build/build.js diff --git a/browserid/static/steal/build/pluginify/parse.js b/resources/static/steal/build/pluginify/parse.js similarity index 100% rename from browserid/static/steal/build/pluginify/parse.js rename to resources/static/steal/build/pluginify/parse.js diff --git a/browserid/static/steal/build/pluginify/pluginify.js b/resources/static/steal/build/pluginify/pluginify.js similarity index 100% rename from browserid/static/steal/build/pluginify/pluginify.js rename to resources/static/steal/build/pluginify/pluginify.js diff --git a/browserid/static/steal/build/pluginify/test/firstFunc.js b/resources/static/steal/build/pluginify/test/firstFunc.js similarity index 100% rename from browserid/static/steal/build/pluginify/test/firstFunc.js rename to resources/static/steal/build/pluginify/test/firstFunc.js diff --git a/browserid/static/steal/build/pluginify/test/pluginify_test.js b/resources/static/steal/build/pluginify/test/pluginify_test.js similarity index 100% rename from browserid/static/steal/build/pluginify/test/pluginify_test.js rename to resources/static/steal/build/pluginify/test/pluginify_test.js diff --git a/browserid/static/steal/build/pluginify/test/secondFunc.js b/resources/static/steal/build/pluginify/test/secondFunc.js similarity index 100% rename from browserid/static/steal/build/pluginify/test/secondFunc.js rename to resources/static/steal/build/pluginify/test/secondFunc.js diff --git a/browserid/static/steal/build/pluginify/test/test_steals.js b/resources/static/steal/build/pluginify/test/test_steals.js similarity index 100% rename from browserid/static/steal/build/pluginify/test/test_steals.js rename to resources/static/steal/build/pluginify/test/test_steals.js diff --git a/browserid/static/steal/build/pluginify/test/weirdRegexps.js b/resources/static/steal/build/pluginify/test/weirdRegexps.js similarity index 100% rename from browserid/static/steal/build/pluginify/test/weirdRegexps.js rename to resources/static/steal/build/pluginify/test/weirdRegexps.js diff --git a/browserid/static/steal/build/pluginify/tokens.js b/resources/static/steal/build/pluginify/tokens.js similarity index 100% rename from browserid/static/steal/build/pluginify/tokens.js rename to resources/static/steal/build/pluginify/tokens.js diff --git a/browserid/static/steal/build/scripts/compiler.jar b/resources/static/steal/build/scripts/compiler.jar similarity index 100% rename from browserid/static/steal/build/scripts/compiler.jar rename to resources/static/steal/build/scripts/compiler.jar diff --git a/browserid/static/steal/build/scripts/scripts.js b/resources/static/steal/build/scripts/scripts.js similarity index 100% rename from browserid/static/steal/build/scripts/scripts.js rename to resources/static/steal/build/scripts/scripts.js diff --git a/browserid/static/steal/build/scripts/yui.jar b/resources/static/steal/build/scripts/yui.jar similarity index 100% rename from browserid/static/steal/build/scripts/yui.jar rename to resources/static/steal/build/scripts/yui.jar diff --git a/browserid/static/steal/build/styles/cssmin.js b/resources/static/steal/build/styles/cssmin.js similarity index 100% rename from browserid/static/steal/build/styles/cssmin.js rename to resources/static/steal/build/styles/cssmin.js diff --git a/browserid/static/steal/build/styles/styles.js b/resources/static/steal/build/styles/styles.js similarity index 100% rename from browserid/static/steal/build/styles/styles.js rename to resources/static/steal/build/styles/styles.js diff --git a/browserid/static/steal/build/styles/test/app/app.css b/resources/static/steal/build/styles/test/app/app.css similarity index 100% rename from browserid/static/steal/build/styles/test/app/app.css rename to resources/static/steal/build/styles/test/app/app.css diff --git a/browserid/static/steal/build/styles/test/app/app.html b/resources/static/steal/build/styles/test/app/app.html similarity index 100% rename from browserid/static/steal/build/styles/test/app/app.html rename to resources/static/steal/build/styles/test/app/app.html diff --git a/browserid/static/steal/build/styles/test/app/app.js b/resources/static/steal/build/styles/test/app/app.js similarity index 100% rename from browserid/static/steal/build/styles/test/app/app.js rename to resources/static/steal/build/styles/test/app/app.js diff --git a/browserid/static/steal/build/styles/test/app/production.css b/resources/static/steal/build/styles/test/app/production.css similarity index 100% rename from browserid/static/steal/build/styles/test/app/production.css rename to resources/static/steal/build/styles/test/app/production.css diff --git a/browserid/static/steal/build/styles/test/css/css1.css b/resources/static/steal/build/styles/test/css/css1.css similarity index 100% rename from browserid/static/steal/build/styles/test/css/css1.css rename to resources/static/steal/build/styles/test/css/css1.css diff --git a/browserid/static/steal/build/styles/test/css/justin.png b/resources/static/steal/build/styles/test/css/justin.png similarity index 100% rename from browserid/static/steal/build/styles/test/css/justin.png rename to resources/static/steal/build/styles/test/css/justin.png diff --git a/browserid/static/steal/build/styles/test/css2.css b/resources/static/steal/build/styles/test/css2.css similarity index 100% rename from browserid/static/steal/build/styles/test/css2.css rename to resources/static/steal/build/styles/test/css2.css diff --git a/browserid/static/steal/build/styles/test/multiline.css b/resources/static/steal/build/styles/test/multiline.css similarity index 100% rename from browserid/static/steal/build/styles/test/multiline.css rename to resources/static/steal/build/styles/test/multiline.css diff --git a/browserid/static/steal/build/styles/test/page.html b/resources/static/steal/build/styles/test/page.html similarity index 100% rename from browserid/static/steal/build/styles/test/page.html rename to resources/static/steal/build/styles/test/page.html diff --git a/browserid/static/steal/build/styles/test/production.css b/resources/static/steal/build/styles/test/production.css similarity index 100% rename from browserid/static/steal/build/styles/test/production.css rename to resources/static/steal/build/styles/test/production.css diff --git a/browserid/static/steal/build/styles/test/productionCompare.css b/resources/static/steal/build/styles/test/productionCompare.css similarity index 100% rename from browserid/static/steal/build/styles/test/productionCompare.css rename to resources/static/steal/build/styles/test/productionCompare.css diff --git a/browserid/static/steal/build/styles/test/styles_test.js b/resources/static/steal/build/styles/test/styles_test.js similarity index 100% rename from browserid/static/steal/build/styles/test/styles_test.js rename to resources/static/steal/build/styles/test/styles_test.js diff --git a/browserid/static/steal/build/styles/test/upload.PNG b/resources/static/steal/build/styles/test/upload.PNG similarity index 100% rename from browserid/static/steal/build/styles/test/upload.PNG rename to resources/static/steal/build/styles/test/upload.PNG diff --git a/browserid/static/steal/build/test/basicpage.html b/resources/static/steal/build/test/basicpage.html similarity index 100% rename from browserid/static/steal/build/test/basicpage.html rename to resources/static/steal/build/test/basicpage.html diff --git a/browserid/static/steal/build/test/basicsource.js b/resources/static/steal/build/test/basicsource.js similarity index 100% rename from browserid/static/steal/build/test/basicsource.js rename to resources/static/steal/build/test/basicsource.js diff --git a/browserid/static/steal/build/test/foreign.html b/resources/static/steal/build/test/foreign.html similarity index 100% rename from browserid/static/steal/build/test/foreign.html rename to resources/static/steal/build/test/foreign.html diff --git a/browserid/static/steal/build/test/foreign.js b/resources/static/steal/build/test/foreign.js similarity index 100% rename from browserid/static/steal/build/test/foreign.js rename to resources/static/steal/build/test/foreign.js diff --git a/browserid/static/steal/build/test/https.html b/resources/static/steal/build/test/https.html similarity index 100% rename from browserid/static/steal/build/test/https.html rename to resources/static/steal/build/test/https.html diff --git a/browserid/static/steal/build/test/https.js b/resources/static/steal/build/test/https.js similarity index 100% rename from browserid/static/steal/build/test/https.js rename to resources/static/steal/build/test/https.js diff --git a/browserid/static/steal/build/test/removecode.js b/resources/static/steal/build/test/removecode.js similarity index 100% rename from browserid/static/steal/build/test/removecode.js rename to resources/static/steal/build/test/removecode.js diff --git a/browserid/static/steal/build/test/run.js b/resources/static/steal/build/test/run.js similarity index 100% rename from browserid/static/steal/build/test/run.js rename to resources/static/steal/build/test/run.js diff --git a/browserid/static/steal/build/test/stealpage.html b/resources/static/steal/build/test/stealpage.html similarity index 100% rename from browserid/static/steal/build/test/stealpage.html rename to resources/static/steal/build/test/stealpage.html diff --git a/browserid/static/steal/build/test/stealprodpage.html b/resources/static/steal/build/test/stealprodpage.html similarity index 100% rename from browserid/static/steal/build/test/stealprodpage.html rename to resources/static/steal/build/test/stealprodpage.html diff --git a/browserid/static/steal/build/test/test.js b/resources/static/steal/build/test/test.js similarity index 100% rename from browserid/static/steal/build/test/test.js rename to resources/static/steal/build/test/test.js diff --git a/browserid/static/steal/buildjs b/resources/static/steal/buildjs similarity index 100% rename from browserid/static/steal/buildjs rename to resources/static/steal/buildjs diff --git a/browserid/static/steal/clean/beautify.js b/resources/static/steal/clean/beautify.js similarity index 100% rename from browserid/static/steal/clean/beautify.js rename to resources/static/steal/clean/beautify.js diff --git a/browserid/static/steal/clean/clean.js b/resources/static/steal/clean/clean.js similarity index 100% rename from browserid/static/steal/clean/clean.js rename to resources/static/steal/clean/clean.js diff --git a/browserid/static/steal/clean/jslint.js b/resources/static/steal/clean/jslint.js similarity index 100% rename from browserid/static/steal/clean/jslint.js rename to resources/static/steal/clean/jslint.js diff --git a/browserid/static/steal/clean/test/clean_test.js b/resources/static/steal/clean/test/clean_test.js similarity index 100% rename from browserid/static/steal/clean/test/clean_test.js rename to resources/static/steal/clean/test/clean_test.js diff --git a/browserid/static/steal/clean/test/test.js b/resources/static/steal/clean/test/test.js similarity index 100% rename from browserid/static/steal/clean/test/test.js rename to resources/static/steal/clean/test/test.js diff --git a/browserid/static/steal/clean/test/testEnd.js b/resources/static/steal/clean/test/testEnd.js similarity index 100% rename from browserid/static/steal/clean/test/testEnd.js rename to resources/static/steal/clean/test/testEnd.js diff --git a/browserid/static/steal/cleanjs b/resources/static/steal/cleanjs similarity index 100% rename from browserid/static/steal/cleanjs rename to resources/static/steal/cleanjs diff --git a/browserid/static/steal/coffee/coffee-script.js b/resources/static/steal/coffee/coffee-script.js similarity index 100% rename from browserid/static/steal/coffee/coffee-script.js rename to resources/static/steal/coffee/coffee-script.js diff --git a/browserid/static/steal/coffee/coffee.js b/resources/static/steal/coffee/coffee.js similarity index 100% rename from browserid/static/steal/coffee/coffee.js rename to resources/static/steal/coffee/coffee.js diff --git a/browserid/static/steal/dev/dev.js b/resources/static/steal/dev/dev.js similarity index 100% rename from browserid/static/steal/dev/dev.js rename to resources/static/steal/dev/dev.js diff --git a/browserid/static/steal/end.js b/resources/static/steal/end.js similarity index 100% rename from browserid/static/steal/end.js rename to resources/static/steal/end.js diff --git a/browserid/static/steal/generate/app b/resources/static/steal/generate/app similarity index 100% rename from browserid/static/steal/generate/app rename to resources/static/steal/generate/app diff --git a/browserid/static/steal/generate/ejs.js b/resources/static/steal/generate/ejs.js similarity index 100% rename from browserid/static/steal/generate/ejs.js rename to resources/static/steal/generate/ejs.js diff --git a/browserid/static/steal/generate/generate.js b/resources/static/steal/generate/generate.js similarity index 100% rename from browserid/static/steal/generate/generate.js rename to resources/static/steal/generate/generate.js diff --git a/browserid/static/steal/generate/inflector.js b/resources/static/steal/generate/inflector.js similarity index 100% rename from browserid/static/steal/generate/inflector.js rename to resources/static/steal/generate/inflector.js diff --git a/browserid/static/steal/generate/system.js b/resources/static/steal/generate/system.js similarity index 100% rename from browserid/static/steal/generate/system.js rename to resources/static/steal/generate/system.js diff --git a/browserid/static/steal/generate/templates/app/(application_name).css.ejs b/resources/static/steal/generate/templates/app/(application_name).css.ejs similarity index 100% rename from browserid/static/steal/generate/templates/app/(application_name).css.ejs rename to resources/static/steal/generate/templates/app/(application_name).css.ejs diff --git a/browserid/static/steal/generate/templates/app/(application_name).html.ejs b/resources/static/steal/generate/templates/app/(application_name).html.ejs similarity index 100% rename from browserid/static/steal/generate/templates/app/(application_name).html.ejs rename to resources/static/steal/generate/templates/app/(application_name).html.ejs diff --git a/browserid/static/steal/generate/templates/app/(application_name).js.ejs b/resources/static/steal/generate/templates/app/(application_name).js.ejs similarity index 100% rename from browserid/static/steal/generate/templates/app/(application_name).js.ejs rename to resources/static/steal/generate/templates/app/(application_name).js.ejs diff --git a/browserid/static/steal/generate/templates/app/docs/.ignore b/resources/static/steal/generate/templates/app/docs/.ignore similarity index 100% rename from browserid/static/steal/generate/templates/app/docs/.ignore rename to resources/static/steal/generate/templates/app/docs/.ignore diff --git a/browserid/static/steal/generate/templates/app/resources/.ignore b/resources/static/steal/generate/templates/app/resources/.ignore similarity index 100% rename from browserid/static/steal/generate/templates/app/resources/.ignore rename to resources/static/steal/generate/templates/app/resources/.ignore diff --git a/browserid/static/steal/generate/templates/app/resources/example.coffee.ejs b/resources/static/steal/generate/templates/app/resources/example.coffee.ejs similarity index 100% rename from browserid/static/steal/generate/templates/app/resources/example.coffee.ejs rename to resources/static/steal/generate/templates/app/resources/example.coffee.ejs diff --git a/browserid/static/steal/generate/templates/app/resources/example.js.ejs b/resources/static/steal/generate/templates/app/resources/example.js.ejs similarity index 100% rename from browserid/static/steal/generate/templates/app/resources/example.js.ejs rename to resources/static/steal/generate/templates/app/resources/example.js.ejs diff --git a/browserid/static/steal/generate/templates/app/resources/example.less.ejs b/resources/static/steal/generate/templates/app/resources/example.less.ejs similarity index 100% rename from browserid/static/steal/generate/templates/app/resources/example.less.ejs rename to resources/static/steal/generate/templates/app/resources/example.less.ejs diff --git a/browserid/static/steal/generate/templates/app/scripts/build.html.ejs b/resources/static/steal/generate/templates/app/scripts/build.html.ejs similarity index 100% rename from browserid/static/steal/generate/templates/app/scripts/build.html.ejs rename to resources/static/steal/generate/templates/app/scripts/build.html.ejs diff --git a/browserid/static/steal/generate/templates/app/scripts/build.js.ejs b/resources/static/steal/generate/templates/app/scripts/build.js.ejs similarity index 100% rename from browserid/static/steal/generate/templates/app/scripts/build.js.ejs rename to resources/static/steal/generate/templates/app/scripts/build.js.ejs diff --git a/browserid/static/steal/generate/templates/app/scripts/clean.js.ejs b/resources/static/steal/generate/templates/app/scripts/clean.js.ejs similarity index 100% rename from browserid/static/steal/generate/templates/app/scripts/clean.js.ejs rename to resources/static/steal/generate/templates/app/scripts/clean.js.ejs diff --git a/browserid/static/steal/generate/templates/app/test/.ignore b/resources/static/steal/generate/templates/app/test/.ignore similarity index 100% rename from browserid/static/steal/generate/templates/app/test/.ignore rename to resources/static/steal/generate/templates/app/test/.ignore diff --git a/browserid/static/steal/generate/templates/page.ejs b/resources/static/steal/generate/templates/page.ejs similarity index 100% rename from browserid/static/steal/generate/templates/page.ejs rename to resources/static/steal/generate/templates/page.ejs diff --git a/browserid/static/steal/generate/test/run.js b/resources/static/steal/generate/test/run.js similarity index 100% rename from browserid/static/steal/generate/test/run.js rename to resources/static/steal/generate/test/run.js diff --git a/browserid/static/steal/get/basic.js b/resources/static/steal/get/basic.js similarity index 100% rename from browserid/static/steal/get/basic.js rename to resources/static/steal/get/basic.js diff --git a/browserid/static/steal/get/dummysteal.js b/resources/static/steal/get/dummysteal.js similarity index 100% rename from browserid/static/steal/get/dummysteal.js rename to resources/static/steal/get/dummysteal.js diff --git a/browserid/static/steal/get/get.js b/resources/static/steal/get/get.js similarity index 100% rename from browserid/static/steal/get/get.js rename to resources/static/steal/get/get.js diff --git a/browserid/static/steal/get/gets.json b/resources/static/steal/get/gets.json similarity index 100% rename from browserid/static/steal/get/gets.json rename to resources/static/steal/get/gets.json diff --git a/browserid/static/steal/get/getter.js b/resources/static/steal/get/getter.js similarity index 100% rename from browserid/static/steal/get/getter.js rename to resources/static/steal/get/getter.js diff --git a/browserid/static/steal/get/git.js b/resources/static/steal/get/git.js similarity index 100% rename from browserid/static/steal/get/git.js rename to resources/static/steal/get/git.js diff --git a/browserid/static/steal/get/github.js b/resources/static/steal/get/github.js similarity index 100% rename from browserid/static/steal/get/github.js rename to resources/static/steal/get/github.js diff --git a/browserid/static/steal/get/json.js b/resources/static/steal/get/json.js similarity index 100% rename from browserid/static/steal/get/json.js rename to resources/static/steal/get/json.js diff --git a/browserid/static/steal/get/test/.gitignore b/resources/static/steal/get/test/.gitignore similarity index 100% rename from browserid/static/steal/get/test/.gitignore rename to resources/static/steal/get/test/.gitignore diff --git a/browserid/static/steal/get/test/.gitmodules b/resources/static/steal/get/test/.gitmodules similarity index 100% rename from browserid/static/steal/get/test/.gitmodules rename to resources/static/steal/get/test/.gitmodules diff --git a/browserid/static/steal/get/test/README b/resources/static/steal/get/test/README similarity index 100% rename from browserid/static/steal/get/test/README rename to resources/static/steal/get/test/README diff --git a/browserid/static/steal/get/test/get_test.js b/resources/static/steal/get/test/get_test.js similarity index 100% rename from browserid/static/steal/get/test/get_test.js rename to resources/static/steal/get/test/get_test.js diff --git a/browserid/static/steal/get/test/stealCode1.js b/resources/static/steal/get/test/stealCode1.js similarity index 100% rename from browserid/static/steal/get/test/stealCode1.js rename to resources/static/steal/get/test/stealCode1.js diff --git a/browserid/static/steal/getjs b/resources/static/steal/getjs similarity index 100% rename from browserid/static/steal/getjs rename to resources/static/steal/getjs diff --git a/browserid/static/steal/js b/resources/static/steal/js similarity index 100% rename from browserid/static/steal/js rename to resources/static/steal/js diff --git a/browserid/static/steal/js.bat b/resources/static/steal/js.bat similarity index 100% rename from browserid/static/steal/js.bat rename to resources/static/steal/js.bat diff --git a/browserid/static/steal/less/less.js b/resources/static/steal/less/less.js similarity index 100% rename from browserid/static/steal/less/less.js rename to resources/static/steal/less/less.js diff --git a/browserid/static/steal/less/less.less b/resources/static/steal/less/less.less similarity index 100% rename from browserid/static/steal/less/less.less rename to resources/static/steal/less/less.less diff --git a/browserid/static/steal/less/less_engine.js b/resources/static/steal/less/less_engine.js similarity index 100% rename from browserid/static/steal/less/less_engine.js rename to resources/static/steal/less/less_engine.js diff --git a/browserid/static/steal/less/less_test.js b/resources/static/steal/less/less_test.js similarity index 100% rename from browserid/static/steal/less/less_test.js rename to resources/static/steal/less/less_test.js diff --git a/browserid/static/steal/less/qunit.html b/resources/static/steal/less/qunit.html similarity index 100% rename from browserid/static/steal/less/qunit.html rename to resources/static/steal/less/qunit.html diff --git a/browserid/static/steal/make.js b/resources/static/steal/make.js similarity index 100% rename from browserid/static/steal/make.js rename to resources/static/steal/make.js diff --git a/browserid/static/steal/parse/parse.js b/resources/static/steal/parse/parse.js similarity index 100% rename from browserid/static/steal/parse/parse.js rename to resources/static/steal/parse/parse.js diff --git a/browserid/static/steal/parse/parse_test.js b/resources/static/steal/parse/parse_test.js similarity index 100% rename from browserid/static/steal/parse/parse_test.js rename to resources/static/steal/parse/parse_test.js diff --git a/browserid/static/steal/parse/test/stealCode1.js b/resources/static/steal/parse/test/stealCode1.js similarity index 100% rename from browserid/static/steal/parse/test/stealCode1.js rename to resources/static/steal/parse/test/stealCode1.js diff --git a/browserid/static/steal/parse/test/testCode.js b/resources/static/steal/parse/test/testCode.js similarity index 100% rename from browserid/static/steal/parse/test/testCode.js rename to resources/static/steal/parse/test/testCode.js diff --git a/browserid/static/steal/parse/tokens.js b/resources/static/steal/parse/tokens.js similarity index 100% rename from browserid/static/steal/parse/tokens.js rename to resources/static/steal/parse/tokens.js diff --git a/browserid/static/steal/patchfile b/resources/static/steal/patchfile similarity index 100% rename from browserid/static/steal/patchfile rename to resources/static/steal/patchfile diff --git a/browserid/static/steal/pluginifyjs b/resources/static/steal/pluginifyjs similarity index 100% rename from browserid/static/steal/pluginifyjs rename to resources/static/steal/pluginifyjs diff --git a/browserid/static/steal/rhino/blank.html b/resources/static/steal/rhino/blank.html similarity index 100% rename from browserid/static/steal/rhino/blank.html rename to resources/static/steal/rhino/blank.html diff --git a/browserid/static/steal/rhino/build.js b/resources/static/steal/rhino/build.js similarity index 100% rename from browserid/static/steal/rhino/build.js rename to resources/static/steal/rhino/build.js diff --git a/browserid/static/steal/rhino/docs.js b/resources/static/steal/rhino/docs.js similarity index 100% rename from browserid/static/steal/rhino/docs.js rename to resources/static/steal/rhino/docs.js diff --git a/browserid/static/steal/rhino/empty.html b/resources/static/steal/rhino/empty.html similarity index 100% rename from browserid/static/steal/rhino/empty.html rename to resources/static/steal/rhino/empty.html diff --git a/browserid/static/steal/rhino/env.js b/resources/static/steal/rhino/env.js similarity index 100% rename from browserid/static/steal/rhino/env.js rename to resources/static/steal/rhino/env.js diff --git a/browserid/static/steal/rhino/file.js b/resources/static/steal/rhino/file.js similarity index 100% rename from browserid/static/steal/rhino/file.js rename to resources/static/steal/rhino/file.js diff --git a/browserid/static/steal/rhino/js.jar b/resources/static/steal/rhino/js.jar similarity index 100% rename from browserid/static/steal/rhino/js.jar rename to resources/static/steal/rhino/js.jar diff --git a/browserid/static/steal/rhino/loader b/resources/static/steal/rhino/loader similarity index 100% rename from browserid/static/steal/rhino/loader rename to resources/static/steal/rhino/loader diff --git a/browserid/static/steal/rhino/loader.bat b/resources/static/steal/rhino/loader.bat similarity index 100% rename from browserid/static/steal/rhino/loader.bat rename to resources/static/steal/rhino/loader.bat diff --git a/browserid/static/steal/rhino/loader.js b/resources/static/steal/rhino/loader.js similarity index 100% rename from browserid/static/steal/rhino/loader.js rename to resources/static/steal/rhino/loader.js diff --git a/browserid/static/steal/rhino/prompt.js b/resources/static/steal/rhino/prompt.js similarity index 100% rename from browserid/static/steal/rhino/prompt.js rename to resources/static/steal/rhino/prompt.js diff --git a/browserid/static/steal/rhino/steal.js b/resources/static/steal/rhino/steal.js similarity index 100% rename from browserid/static/steal/rhino/steal.js rename to resources/static/steal/rhino/steal.js diff --git a/browserid/static/steal/rhino/test.js b/resources/static/steal/rhino/test.js similarity index 100% rename from browserid/static/steal/rhino/test.js rename to resources/static/steal/rhino/test.js diff --git a/browserid/static/steal/rhino/utils.js b/resources/static/steal/rhino/utils.js similarity index 100% rename from browserid/static/steal/rhino/utils.js rename to resources/static/steal/rhino/utils.js diff --git a/browserid/static/steal/steal.js b/resources/static/steal/steal.js similarity index 100% rename from browserid/static/steal/steal.js rename to resources/static/steal/steal.js diff --git a/browserid/static/steal/steal.production.js b/resources/static/steal/steal.production.js similarity index 100% rename from browserid/static/steal/steal.production.js rename to resources/static/steal/steal.production.js diff --git a/browserid/static/steal/test/absoluteurl.html b/resources/static/steal/test/absoluteurl.html similarity index 100% rename from browserid/static/steal/test/absoluteurl.html rename to resources/static/steal/test/absoluteurl.html diff --git a/browserid/static/steal/test/absoluteurl/absoluteurl.js b/resources/static/steal/test/absoluteurl/absoluteurl.js similarity index 100% rename from browserid/static/steal/test/absoluteurl/absoluteurl.js rename to resources/static/steal/test/absoluteurl/absoluteurl.js diff --git a/browserid/static/steal/test/absoluteurl/alert.js b/resources/static/steal/test/absoluteurl/alert.js similarity index 100% rename from browserid/static/steal/test/absoluteurl/alert.js rename to resources/static/steal/test/absoluteurl/alert.js diff --git a/browserid/static/steal/test/another/two.js b/resources/static/steal/test/another/two.js similarity index 100% rename from browserid/static/steal/test/another/two.js rename to resources/static/steal/test/another/two.js diff --git a/browserid/static/steal/test/envjs/qunit.html b/resources/static/steal/test/envjs/qunit.html similarity index 100% rename from browserid/static/steal/test/envjs/qunit.html rename to resources/static/steal/test/envjs/qunit.html diff --git a/browserid/static/steal/test/envjs/qunit.js b/resources/static/steal/test/envjs/qunit.js similarity index 100% rename from browserid/static/steal/test/envjs/qunit.js rename to resources/static/steal/test/envjs/qunit.js diff --git a/browserid/static/steal/test/funcunit.html b/resources/static/steal/test/funcunit.html similarity index 100% rename from browserid/static/steal/test/funcunit.html rename to resources/static/steal/test/funcunit.html diff --git a/browserid/static/steal/test/funcunit/funcunit.js b/resources/static/steal/test/funcunit/funcunit.js similarity index 100% rename from browserid/static/steal/test/funcunit/funcunit.js rename to resources/static/steal/test/funcunit/funcunit.js diff --git a/browserid/static/steal/test/funcunit/steal_test.js b/resources/static/steal/test/funcunit/steal_test.js similarity index 100% rename from browserid/static/steal/test/funcunit/steal_test.js rename to resources/static/steal/test/funcunit/steal_test.js diff --git a/browserid/static/steal/test/one/four.js b/resources/static/steal/test/one/four.js similarity index 100% rename from browserid/static/steal/test/one/four.js rename to resources/static/steal/test/one/four.js diff --git a/browserid/static/steal/test/one/one.js b/resources/static/steal/test/one/one.js similarity index 100% rename from browserid/static/steal/test/one/one.js rename to resources/static/steal/test/one/one.js diff --git a/browserid/static/steal/test/qunit.html b/resources/static/steal/test/qunit.html similarity index 100% rename from browserid/static/steal/test/qunit.html rename to resources/static/steal/test/qunit.html diff --git a/browserid/static/steal/test/qunit/one.css b/resources/static/steal/test/qunit/one.css similarity index 100% rename from browserid/static/steal/test/qunit/one.css rename to resources/static/steal/test/qunit/one.css diff --git a/browserid/static/steal/test/qunit/qunit.js b/resources/static/steal/test/qunit/qunit.js similarity index 100% rename from browserid/static/steal/test/qunit/qunit.js rename to resources/static/steal/test/qunit/qunit.js diff --git a/browserid/static/steal/test/qunit/steal_test.js b/resources/static/steal/test/qunit/steal_test.js similarity index 100% rename from browserid/static/steal/test/qunit/steal_test.js rename to resources/static/steal/test/qunit/steal_test.js diff --git a/browserid/static/steal/test/run.js b/resources/static/steal/test/run.js similarity index 100% rename from browserid/static/steal/test/run.js rename to resources/static/steal/test/run.js diff --git a/browserid/static/steal/test/steal.html b/resources/static/steal/test/steal.html similarity index 100% rename from browserid/static/steal/test/steal.html rename to resources/static/steal/test/steal.html diff --git a/browserid/static/steal/test/test.js b/resources/static/steal/test/test.js similarity index 100% rename from browserid/static/steal/test/test.js rename to resources/static/steal/test/test.js diff --git a/browserid/static/steal/test/three.js b/resources/static/steal/test/three.js similarity index 100% rename from browserid/static/steal/test/three.js rename to resources/static/steal/test/three.js diff --git a/browserid/static/steal/test/two.css b/resources/static/steal/test/two.css similarity index 100% rename from browserid/static/steal/test/two.css rename to resources/static/steal/test/two.css diff --git a/browserid/static/steal/update b/resources/static/steal/update similarity index 100% rename from browserid/static/steal/update rename to resources/static/steal/update diff --git a/browserid/views/about.ejs b/resources/views/about.ejs similarity index 95% rename from browserid/views/about.ejs rename to resources/views/about.ejs index f8bd46c0a6e8c8abb0a579e8442f7056fca9c1a1..3bfe7009df690c0bb641074c635702e0332f3210 100644 --- a/browserid/views/about.ejs +++ b/resources/views/about.ejs @@ -1,4 +1,4 @@ -<div id="content"> +<div id="content" class="display_always"> <div id="about"> <div class="video"> diff --git a/resources/views/dialog.ejs b/resources/views/dialog.ejs new file mode 100644 index 0000000000000000000000000000000000000000..d2b634a26338b2e79f40b510c8351d1aba641e22 --- /dev/null +++ b/resources/views/dialog.ejs @@ -0,0 +1,35 @@ + <section id="formWrap"> + <form novalidate> + <div id="favicon"> + <div class="vertical"> + <strong id="sitename"></strong> + </div> + </div> + + <div id="signIn"> + <div class="arrow"></div> + <div class="table"> + <div class="vertical contents"> + </div> + </div> + </div> + </form> + </section> + + + <section id="wait"> + <div class="table"> + <div class="vertical contents"> + <h2>Communicating with server</h2> + <p>Just a moment while we talk with the server.</p> + </div> + </div> + </section> + + + <section id="error"> + <div class="table"> + <div class="vertical contents"> + </div> + </div> + </section> diff --git a/browserid/views/dialog.ejs b/resources/views/dialog_layout.ejs similarity index 53% rename from browserid/views/dialog.ejs rename to resources/views/dialog_layout.ejs index bf766dfdae7a176d976d3fca573e38a5638efab0..13dd4d98f92ddc60880e28d7105492ba153837ae 100644 --- a/browserid/views/dialog.ejs +++ b/resources/views/dialog_layout.ejs @@ -24,41 +24,7 @@ </header> <div id="content"> - <section id="formWrap"> - <form novalidate> - <div id="favicon"> - <div class="vertical"> - <strong id="sitename"></strong> - </div> - </div> - - <div id="signIn"> - <div class="arrow"></div> - <div class="table"> - <div class="vertical contents"> - </div> - </div> - </div> - </form> - </section> - - - <section id="wait"> - <div class="table"> - <div class="vertical contents"> - <h2>Communicating with server</h2> - <p>Just a moment while we talk with the server.</p> - </div> - </div> - </section> - - - <section id="error"> - <div class="table"> - <div class="vertical contents"> - </div> - </div> - </section> + <%- body %> </div> <footer> @@ -79,12 +45,14 @@ </div> - <script type="text/html" id="templateTooltip"> - <div class="tooltip"> - {{ contents }} - </div> - </script> - <script type="text/javascript" src="/vepbundle"></script> - <script type="text/javascript" src="steal/steal<%= production ? '.production' : '' %>.js?dialog"></script> + <% if (useJavascript !== false) { %> + <script type="text/html" id="templateTooltip"> + <div class="tooltip"> + {{ contents }} + </div> + </script> + <script type="text/javascript" src="/vepbundle"></script> + <script type="text/javascript" src="steal/steal<%= production ? '.production' : '' %>.js?dialog"></script> + <% } %> </body> </html> diff --git a/browserid/views/forgot.ejs b/resources/views/forgot.ejs similarity index 55% rename from browserid/views/forgot.ejs rename to resources/views/forgot.ejs index b280f3f463253fa63f3673465396e16c2031b611..763420c42f8d2d04d8ee660550bb85a42fd0a2ab 100644 --- a/browserid/views/forgot.ejs +++ b/resources/views/forgot.ejs @@ -1,7 +1,7 @@ -<div id="vAlign"> +<div id="vAlign" class="display_always"> <div id="signUpFormWrap"> <!-- XXX this form submits to nowhere --> - <form id="signUpForm" class="cf authform"> + <form id="signUpForm" class="cf authform" novalidate> <h1 class="serif">Forgot Password</h1> <div class="notifications"> <div class="notification error doh">Doh! Something went wrong :-( </div> @@ -12,8 +12,26 @@ <ul class="inputs"> <li> <label class="serif" for="email">Email Address</label> - <input class="sans" id="email" autofocus required placeholder="Your Email" type="email" x-moz-errormessage="Please enter the email address you would like to use"> + <input class="sans" id="email" autofocus required placeholder="Your Email" type="email" autocapitalize="off" autocorrect="off" maxlength="254" /> + + <div id="email_format" class="tooltip" for="email"> + This field must be an email address. + </div> + + <div id="email_required" class="tooltip" for="email"> + The email field is required. + </div> + + <div id="could_not_add" class="tooltip" for="email"> + We just sent an email to that address! If you really want to send another, wait a minute or two and try again. + </div> + + <div id="not_registered" class="tooltip" for="email"> + Non existent user! + </div> </li> + + </ul> <div class="submit cf"> <div class="remember cf"> diff --git a/resources/views/index.ejs b/resources/views/index.ejs new file mode 100644 index 0000000000000000000000000000000000000000..6873159afed75c94a1ed99549570c79f19f4ad85 --- /dev/null +++ b/resources/views/index.ejs @@ -0,0 +1,43 @@ + <div id="content" style="display:none" class="display_auth"> + <div id="manage"> + <h1 class="serif">Account Manager</h1> + <div class="edit cf"> + <strong>Your Email Addresses</strong> + + <a id="manageAccounts" href="#">edit</a> + <a id="cancelManage" href="#">done</a> + </div> + <ul id="emailList"> + </ul> + <div id="disclaimer">You may, at any time, <a href="#" id="cancelAccount">cancel your account</a></div> + </div> + </div> + + <div id="vAlign" class="display_nonauth"> + <div id="signUp"> + <div id="card"><img src="/i/slit.png"></div> + <div id="hint"></div> + <div id="status"></div> + + <p>Connect with <em>BrowserID</em>, the safest & easiest way to sign in.</p> + <p> + <a class="granted info" href="/about">Take the tour</a> + <span class="require-js">or + <a href="/signup" class="button granted create">sign up</a> + </span> + </p> + </div> + </div> + + <script type="text/html" id="templateUser"> + <li class="identity cf"> + <div class="email">{{ email }}</div> + <div class="activity cf"> + <button class="delete">remove</button> + <!-- removed registration info. We want to replace this with Last Used At ... --> + <!-- <abbr title="Registered: {{ created }}" class="status">Registered {{ relative }}.</abbr>--> + </div> + </li> + </script> + + diff --git a/browserid/views/layout.ejs b/resources/views/layout.ejs similarity index 94% rename from browserid/views/layout.ejs rename to resources/views/layout.ejs index d012c52ef50d190e05aeb0348c131e27f1b279d4..c8009278cbd81dbc653f4d5c123d01671a6733a7 100644 --- a/browserid/views/layout.ejs +++ b/resources/views/layout.ejs @@ -19,6 +19,8 @@ <script src="/js/json2.js" type="text/javascript"></script> <script src="/dialog/resources/underscore-min.js" type="text/javascript"></script> <script src="/dialog/resources/browserid-extensions.js" type="text/javascript"></script> + <script src="/dialog/resources/browserid.js" type="text/javascript"></script> + <script src="/js/page_helpers.js" type="text/javascript"></script> <script src="/js/browserid.js" type="text/javascript"></script> <script src="/js/pages/index.js" type="text/javascript"></script> <script src="/dialog/resources/storage.js" type="text/javascript"></script> @@ -33,7 +35,7 @@ <script src="/js/pages/signin.js" type="text/javascript"></script> <script src="/js/pages/signup.js" type="text/javascript"></script> <% } %> - <title><%- title %></title> + <title>BrowserID: <%- title %></title> </head> <body> @@ -43,6 +45,7 @@ <ul class="cf"> <li><a class="home" href="/"></a></li> </ul> + <ul class="nav cf"> <li><a href="/about">How it works</a></li> <li><a href="https://github.com/mozilla/browserid/wiki/How-to-Use-BrowserID-on-Your-Site" target="_blank">Developers</a></li> diff --git a/browserid/views/privacy.ejs b/resources/views/privacy.ejs similarity index 95% rename from browserid/views/privacy.ejs rename to resources/views/privacy.ejs index 1c007ff475b0a6245e355f97805f15da4738a73b..07b662d193210086615d8be2ea35f2702c41a1a3 100644 --- a/browserid/views/privacy.ejs +++ b/resources/views/privacy.ejs @@ -1,4 +1,4 @@ -<div id="content"> +<div id="content" class="display_always"> <div id="legal"> <h2>Privacy & BrowserID</h2> @@ -57,7 +57,7 @@ <p>Mozilla is an open organization that believes in sharing as much information as possible about its products, its operations, and its associations with its wider community. As such, BrowserID Service users should expect that Mozilla will make all Usage Statistics publicly available at some point. However, any publicly available Usage Statistics will only be reported on an aggregate, anonymous basis. No Personal Information or Potentially Personal Information will be available in any of these public reports.</p> <h5>How to Disable or Opt-Out of BrowserID</h5> - <p>If at any time, you decide you no longer want to use the BrowserID Service, you may cancel your BrowserID Account by visiting <tt>https://browserid.org/</tt>, signing in using any of your email addresses and your password, clicking the "edit" button, and clicking "remove" next to each of your email addresses.</p> + <p>If at any time, you decide you no longer want to use the BrowserID Service, you may cancel your BrowserID Account by visiting <kbd>https://browserid.org/</kbd>, signing in using any of your email addresses and your password, clicking the "edit" button, and clicking "remove" next to each of your email addresses.</p> <h5>Other Disclosures</h5> <p>In certain other limited situations, Mozilla may disclose your Personal Information, such as when necessary to protect our websites and operations (e.g., against attacks); to protect the rights, privacy, safety, or property of Mozilla or its users; to enforce our terms of service; and to pursue available legal remedies. Additionally, Mozilla may need to transfer Personal Information to an affiliate or successor in the event of a change of our corporate structure or status, such as in the event of a restructuring, sale, or bankruptcy.</p> @@ -79,14 +79,14 @@ <h5>For More Information</h5> <p>You may request access, correction, or deletion of Personal Information or Potentially Personal Information, as permitted by law. We will seek to comply with such requests, provided that we have sufficient information to identify the Personal Information or Potentially Personal Information related to you. Any such requests or other questions or concerns regarding this Policy and Mozilla's data protection practices should be addressed to:</p> - <blockquote><pre> - Mozilla Corporation - Attn: Legal Notices – Privacy - 650 Castro Street, Suite 300 - Mountain View, CA 94041-2072 - Phone: +1-650-903-0800 - E-mail: privacy@mozilla.com - </pre> - </blockquote> +<blockquote><pre> +Mozilla Corporation +Attn: Legal Notices – Privacy +650 Castro Street, Suite 300 +Mountain View, CA 94041-2072 +Phone: +1-650-903-0800 +E-mail: <a href="mailto:privacy@mozilla.com">privacy@mozilla.com</a> +</pre> +</blockquote> </div> </div> diff --git a/browserid/views/relay.ejs b/resources/views/relay.ejs similarity index 62% rename from browserid/views/relay.ejs rename to resources/views/relay.ejs index b6f4cc4f87f190231ac5fffe8912ea1a9c778823..1fc38b014dd59ab903ef989de9604ece89eb63c2 100644 --- a/browserid/views/relay.ejs +++ b/resources/views/relay.ejs @@ -13,8 +13,20 @@ <% } else { %> <script type='text/javascript' src='https://browserid.org/dialog/resources/jschannel.js'></script> + <script type='text/javascript' + src='https://browserid.org/dialog/resources/browserid.js'></script> <script type='text/javascript' src='https://browserid.org/relay/relay.js'></script> <% } %> + + <script type="text/javascript"> + var relay = BrowserID.Relay; + relay.init({ + channel: Channel, + window: window.parent + }); + + relay.open(); + </script> </body> </html> diff --git a/browserid/views/signin.ejs b/resources/views/signin.ejs similarity index 89% rename from browserid/views/signin.ejs rename to resources/views/signin.ejs index 54089605e911f90aa02d54fbdd087e51f17f8548..9c4a90874ac5ec267182e9152746cf911e3ed1be 100644 --- a/browserid/views/signin.ejs +++ b/resources/views/signin.ejs @@ -1,4 +1,4 @@ -<div id="vAlign"> +<div id="vAlign" class="disply_always"> <div id="signUpFormWrap"> <!-- XXX this form submits to nowhere --> <form id="signUpForm" class="cf authform" novalidate> @@ -11,7 +11,7 @@ <ul class="inputs"> <li> <label class="serif" for="email">Email Address</label> - <input class="sans" id="email" autofocus placeholder="Your Email" type="email" tabindex="1"> + <input class="sans" id="email" autofocus placeholder="Your Email" type="email" autocapitalize="off" autocorrect="off" tabindex="1" maxlength="254" /> <div id="email_format" class="tooltip" for="email"> This field must be an email address. @@ -49,3 +49,7 @@ </div> </div> +<noscript> + We're sorry, but to sign in to BrowserID, you must have Javascript enabled. +</noscript> + diff --git a/browserid/views/signup.ejs b/resources/views/signup.ejs similarity index 86% rename from browserid/views/signup.ejs rename to resources/views/signup.ejs index 29a6f662fbeba3818ed61d68d665b5386ce15b33..cc95cd345924ba515e9ee5f54addbfb4b930003a 100644 --- a/browserid/views/signup.ejs +++ b/resources/views/signup.ejs @@ -1,4 +1,4 @@ -<div id="vAlign"> +<div id="vAlign" class="display_always"> <div id="signUpFormWrap"> <!-- XXX this form submits to nowhere --> <form id="signUpForm" class="cf authform" novalidate> @@ -13,7 +13,7 @@ <ul class="inputs forminputs"> <li> <label class="serif" for="email">Email Address</label> - <input class="sans" id="email" autofocus placeholder="Your Email" type="email" /> + <input class="sans" id="email" autofocus placeholder="Your Email" type="email" autocapitalize="off" autocorrect="off" maxlength="254" /> <div id="email_format" class="tooltip" for="email"> This field must be an email address. @@ -36,3 +36,9 @@ </div> </div> +<noscript> + We're sorry, but to sign up for BrowserID, you must have Javascript enabled. +</noscript> + + + diff --git a/browserid/views/tos.ejs b/resources/views/tos.ejs similarity index 99% rename from browserid/views/tos.ejs rename to resources/views/tos.ejs index 53bae5ecb4ceed9c3164dcacbb091e6d6f9f892c..fe33bca8de3621d044749c3b821ac6a702585014 100644 --- a/browserid/views/tos.ejs +++ b/resources/views/tos.ejs @@ -1,4 +1,4 @@ -<div id="content"> +<div id="content" class="display_always"> <div id="legal"> <h2>Terms of Service — Overview</h2> diff --git a/resources/views/unsupported_dialog.ejs b/resources/views/unsupported_dialog.ejs new file mode 100644 index 0000000000000000000000000000000000000000..eac8bf8ade00ba07b18986924c30729c6a355c2f --- /dev/null +++ b/resources/views/unsupported_dialog.ejs @@ -0,0 +1,28 @@ + <section id="error" style="display: block" class="unsupported"> + <div class="table"> + <div class="vertical contents"> + <div id="reason"> + We're sorry, but currently your browser isn't supported. + </div> + + <div id="alternative"> + + <div id="borderbox"> + <a href="http://getfirefox.com" target="_blank"> + <img src="/i/firefox_logo.png" width="250" height="88" alt="Firefox logo" /> + </a> + + <p> + BrowserID works with <a href="http://getfirefox.com" target="_blank" title="Get Firefox">Firefox</a> + </p> + + <p class="lighter"> + and other <a href="http://whatbrowser.org" target="_blank">modern browsers.</a> + </p> + </div> + + </div> + + </div> + </div> + </section> diff --git a/browserid/views/verifyemail.ejs b/resources/views/verifyemail.ejs similarity index 82% rename from browserid/views/verifyemail.ejs rename to resources/views/verifyemail.ejs index ea5f075deb102ca457dec25ac992045f83a6e2ef..d9ee49385494c4069cba00874bbe0023e661a419 100644 --- a/browserid/views/verifyemail.ejs +++ b/resources/views/verifyemail.ejs @@ -1,4 +1,4 @@ -<div id="vAlign"> +<div id="vAlign" class="display_always"> <div id="signUpFormWrap"> <div id="signUpForm" class="cf"> <h1 class="serif">Email Verification</h1> @@ -17,9 +17,9 @@ <strong id="email">Your address</strong> has been verified! <span id="siteinfo"> - Your new address has been used to sign in to + Your new address is set up and you should now be signed in. + You may now close this window and go back to <strong class="website"></strong> - which is shown in its original window or tab. </span> </p> </div> diff --git a/browserid/views/verifyuser.ejs b/resources/views/verifyuser.ejs similarity index 94% rename from browserid/views/verifyuser.ejs rename to resources/views/verifyuser.ejs index 3cca2c5284310bb92600274b5e04e6efc1b181fb..b173f3580063d842a085e950083669dabf57a4d5 100644 --- a/browserid/views/verifyuser.ejs +++ b/resources/views/verifyuser.ejs @@ -1,4 +1,4 @@ -<div id="vAlign"> +<div id="vAlign" class="display_always"> <div id="signUpFormWrap"> <ul class="notifications"> <li class="notification error" id="badtoken">There was a problem with your signup link. Has this address already been registered?</li> @@ -14,7 +14,7 @@ <ul class="inputs"> <li> <label class="serif" for="email">Email Address</label> - <input class="youraddress sans" id="email" placeholder="Your Email" type="email" value="" disabled="disabled"> + <input class="youraddress sans" id="email" placeholder="Your Email" type="email" value="" disabled="disabled" maxlength="254"> </li> <li> <label class="serif" for="password">New Password</label> diff --git a/rp/index2.html b/rp/index2.html deleted file mode 100644 index cdc25d3781af1b7fc01affcba7c6b7276833ed2c..0000000000000000000000000000000000000000 --- a/rp/index2.html +++ /dev/null @@ -1,141 +0,0 @@ -<!DOCTYPE html> -<html> -<head> -<title> -BrowserID Relying Party -</title> -<link href='http://fonts.googleapis.com/css?family=Permanent+Marker' rel='stylesheet' type='text/css'> -<style type="text/css"> - -body { margin: auto; font: 13px/1.5 Helvetica, Arial, 'Liberation Sans', FreeSans, sans-serif; } -a:link, a:visited { font-style: italic; text-decoration: none; color: #008; } -a:hover { border-bottom: 2px solid black ; } -.number { font-family: 'Permanent Marker', arial, serif; font-size: 4em; float: left; padding: 0; margin: 0; vertical-align: top; width: 1.3em} -.title { font-size: 2em; font-weight: bold; text-align: center; margin: 1.5em; } -.intro { font-size: 1.2em; width: 600px; margin: auto; } -.step { width: 600px; margin: auto; margin-top: 1em;} -.desc { padding-top: 1.5em; min-height: 4.5em;} -.output { - font-family: 'lucida console', monaco, 'andale mono', 'bitstream vera sans mono', consolas, monospace; - border: 3px solid #666; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - border-radius: 4px; - padding: .5em; - margin: .5em; - color: #ccc; - background-color: #333; -/* white-space: pre;*/ - font-size: .9em; - width:600px; - word-wrap: break-word; -} - -</style> -</head> -<body> -<div class="title"> - Example BrowserID Relying Party - Specific Identity -</div> - -<div class="intro"> - This is the simplest possible (stateless, static, client-only) BrowserID Relying Party. It - demonstrates the steps required to use BrowserID to verify the identity of a user.<br /> - <a id="tokenGetter" href="#">Click here to preauth</a>. <span id="token"></span><br /> - <a id="badpartyStarter" href="#">Click here to kick off the bad party</a>.<br /> - <a id="partyStarter" href="#">Click here to kick off the party</a>.<br /> Here's what will happen: -</div> - -<div class="step"> - <div class="number">1.</div> - <div class="desc"><b>Browser Interaction:</b> Upon clicking the link above, the webpage will call <tt>navigator.id.getSpecificVerifiedEmail()</tt> to indicate to the browser that it would like an identity for the user</div> -</div> - -<div class="step"> - <div class="number">2.</div> - <div class="desc"><b>User Interaction:</b> The browser will spawn a dialog that the user can interact with the select what identity they want to provide to the site </div> -</div> - -<div class="step"> - <div class="number">3.</div> - <div class="desc"><b>Assertion Generation:</b> Upon selection of an identity, an <i>assertion</i> will be returned to the webpage via a callback, it looks like this: </div> - <div class="output" id="oAssertion">...waiting for party commencement...</div> -</div> - -<div class="step"> - <div class="number">4.</div> - <div class="desc"><b>Assertion Verification:</b> This site can then send that assertion up to a <i>verification server</i> which cryptographically checks that the identity embedded in the verification actually belongs to the browser in use by the monkey at the keyboard. The request looks like </div> - <div class="output" id="oVerificationRequest">...waiting for party commencement...</div> -</div> - -<div class="step"> - <div class="number">5.</div> - <div class="desc"><b>Verification Response</b>: The verification server responds to the site to tell it whether or not the verification blob is valid:</div> - <div class="output" id="oVerificationResponse">...waiting for party commencement...</div> -</div> - -<div class="step"> - <div class="number">6.</div> - <div class="desc"><b>All Done!</b> The site can now create an account keyed on the users identity (email address), set cookies, etc! Signing in again is just re-running these same steps.</div> -</div> - -</body> -<script src="jquery-min.js"></script> -<script src="https://browserid.org/include.js"></script> -<script> - function dumpObject(obj) { - var htmlRep = ""; - for (var k in obj) { - if (obj.hasOwnProperty(k) && typeof obj[k] === 'string') { - htmlRep += "<b>" + k + ":</b> " + obj[k] + "<br/>"; - } else if (k === 'valid-until') { - htmlRep += "<b>" + k + ":</b> " + (new Date(obj[k])).toString() + "<br/>"; - } - - } - return htmlRep; - } - - function start_the_party(email, token) { - navigator.id.getSpecificVerifiedEmail(email, token, function(assertion) { - // Now we'll send this assertion over to the verification server for validation - $("#oAssertion").empty().html(dumpObject(assertion)); - - var url = "http://browserid.org/verify?assertion=" + window.encodeURIComponent(assertion) + - "&audience=" + window.encodeURIComponent("rp.eyedee.me"); - $("#oVerificationRequest").empty().text(url); - - $.ajax({ - url: url, - success: function(data, textStatus, jqXHR) { - $("#oVerificationResponse").empty().text(JSON.stringify(data, null, 4)); - }, - error: function(jqXHR, textStatus, errorThrown) { - $("#oVerificationResponse").empty().text(jqXHR.responseText); - } - }) - }, function(code, msg) { - alert("something very bad happened! ("+code+"): " + msg); - }); - } - - $(document).ready(function() { - var GUID = null; - $("#tokenGetter").click(function() { - navigator.id.preauthEmail("ben@adida.net", function(guid) { - $('#token').html(guid); - GUID=guid; - }); - }); - - $("#partyStarter").click(function() { - start_the_party('ben@adida.net', GUID); - }); - - $("#badpartyStarter").click(function() { - start_the_party('ben2@adida.net', GUID); - }); - }); -</script> - -</html> diff --git a/run.js b/run.js deleted file mode 100755 index 0d82815313779d4df0fe965102506067b9801eaa..0000000000000000000000000000000000000000 --- a/run.js +++ /dev/null @@ -1,189 +0,0 @@ -#!/usr/bin/env node - -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Mozilla BrowserID. - * - * The Initial Developer of the Original Code is Mozilla. - * Portions created by the Initial Developer are Copyright (C) 2011 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -// a little node webserver designed to run the unit tests herein - -var sys = require("sys"), - http = require("http"), - url = require("url"), - path = require("path"), - fs = require("fs"), - express = require("express"), -substitution = require('./libs/substitute.js'); - -// when running under the harness, let's also output log messages to the terminal -require('./libs/logging.js').enableConsoleLogging(); - -var configuration = require('./libs/configuration.js'); - -var PRIMARY_HOST = process.env.IP_ADDRESS || "127.0.0.1"; - -var boundServers = [ ]; - -var subs = undefined; -function substitutionMiddleware(req, resp, next) { - if (!subs) { - subs = { }; - for (var i = 0; i < boundServers.length; i++) { - var o = boundServers[i] - var a = o.server.address(); - var from = o.name; - var to = "http://" + a.address + ":" + a.port; - subs[from] = to; - - // now do another replacement to catch bare hostnames sans http(s) - // and explicit cases where port is appended - var fromWithPort; - if (from.substr(0,5) === 'https') { - from = from.substr(8); - fromWithPort = from + ":443"; - } else { - from = from.substr(7); - fromWithPort = from + ":80"; - } - to = to.substr(7); - - if (o.subPath) to += o.subPath; - - subs[fromWithPort] = to; - subs[from] = to; - } - } - (substitution.substitute(subs))(req, resp, next); -} - -function createServer(obj) { - var app = express.createServer(); - - // this file is a *test* harness, to make it go, we'll insert a little - // handler that substitutes output, changing production URLs to - // developement URLs. - app.use(substitutionMiddleware); - - // let the specific server interact directly with the express server to - // register their middleware, routes, etc... - if (obj.setup) obj.setup(app); - - // now set up the static resource servin' - var p = obj.path, ps = path.join(p, "static"); - try { if (fs.statSync(ps).isDirectory()) p = ps; } catch(e) { } - app.use(express.static(p)); - - // and listen! - app.listen(obj.port, PRIMARY_HOST); - return app; -}; - -// start up webservers on ephemeral ports for each subdirectory here. -var dirs = [ - // the reference verification server. A version is hosted at - // browserid.org and may be used, or the RP may perform their - // own verification. - { - name: "https://browserid.org/verify", - subPath: "/", - path: path.join(__dirname, "verifier") - }, - // An example relying party. - { - name: "http://rp.eyedee.me", - path: path.join(__dirname, "rp") - }, - - // BrowserID: the secondary + ip + more. - { - name: "https://browserid.org", - path: path.join(__dirname, "browserid") - } -]; - -function formatLink(server, extraPath) { - var addr = server.address(); - var url = 'http://' + addr.address + ':' + addr.port; - if (extraPath) { - url += extraPath; - } - return url; -} - -console.log("Running test servers:"); - -var port_num=10000; -dirs.forEach(function(dirObj) { - if (!fs.statSync(dirObj.path).isDirectory()) return; - // does this server have a js handler for custom request handling? - var handlerPath = path.join(dirObj.path, "app.js"); - var runJS = {}; - try { - var runJSExists = false; - try { runJSExists = fs.statSync(handlerPath).isFile() } catch(e) {}; - if (runJSExists) runJS = require(handlerPath); - } catch(e) { - console.log("Error loading " + handlerPath + ": " + e); - process.exit(1); - } - - var so = { - path: dirObj.path, - server: undefined, - port: port_num++, - name: dirObj.name, - handler: runJS.handler, - setup: runJS.setup, - shutdown: runJS.shutdown, - subPath: dirObj.subPath - }; - so.server = createServer(so) - boundServers.push(so); - console.log(" " + dirObj.name + ": " + formatLink(so.server)); -}); - -process.on('SIGINT', function () { - console.log('\nSIGINT recieved! trying to shut down gracefully...'); - boundServers.forEach(function(bs) { - if (bs.shutdown) bs.shutdown(); - bs.server.on('close', function() { - console.log("server shutdown,", bs.server.connections, "connections still open..."); - }); - bs.server.close(); - }); - // exit more harshly in 700ms - setTimeout(function() { - console.log("exiting..."); - process.exit(0); - }, 700); -}); diff --git a/scripts/browserid.spec b/scripts/browserid.spec new file mode 100644 index 0000000000000000000000000000000000000000..368194a57e5c834075678777fc41efd46ef658c4 --- /dev/null +++ b/scripts/browserid.spec @@ -0,0 +1,45 @@ +%define _rootdir /opt/browserid + +Name: browserid-server +Version: 0.2011.10.13 +Release: 1%{?dist} +Summary: BrowserID server +Packager: Pete Fritchman <petef@mozilla.com> +Group: Development/Libraries +License: MPL 1.1+/GPL 2.0+/LGPL 2.1+ +URL: https://github.com/mozilla/browserid +Source0: %{name}.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root +AutoReqProv: no +Requires: openssl nodejs +BuildRequires: gcc-c++ git jre make npm openssl-devel + +%description +browserid server & web home for browserid.org + +%prep +%setup -q -n browserid + +%build +npm install +export PATH=$PWD/node_modules/.bin:$PATH +(cd browserid && ./compress.sh) +git log -1 --oneline > browserid/static/ver.txt + +%install +rm -rf %{buildroot} +mkdir -p %{buildroot}%{_rootdir} +for f in browserid libs node_modules verifier *.json *.js; do + cp -rp $f %{buildroot}%{_rootdir}/$dir +done + +%clean +rm -rf %{buildroot} + +%files +%defattr(-,root,root,-) +%{_rootdir} + +%changelog +* Tue Oct 18 2011 Pete Fritchman <petef@mozilla.com> +- Initial version diff --git a/browserid/compress.sh b/scripts/compress.sh similarity index 61% rename from browserid/compress.sh rename to scripts/compress.sh index 3a493518a47c68c4aae8037fcf3931ac12a50e2f..6233646e5d25ab275e4efb3e87b30ae77697922e 100755 --- a/browserid/compress.sh +++ b/scripts/compress.sh @@ -1,5 +1,7 @@ #!/bin/sh +cd $(dirname "$0")/.. + UGLIFY=`which uglifyjs 2> /dev/null` if [ ! -x "$UGLIFY" ]; then echo "uglifyjs not found in your path. can't create production resources. disaster." @@ -12,13 +14,13 @@ if [ ! -x "$JAVA" ]; then exit 1 fi -YUI_LOCATION=`pwd`'/static/steal/build/scripts/yui.jar' +YUI_LOCATION=`pwd`'/resources/static/steal/build/scripts/yui.jar' echo '' echo '****Compressing include.js****' echo '' -cd static +cd resources/static mv include.js include.orig.js $UGLIFY include.orig.js > include.js @@ -37,7 +39,7 @@ cat popup.css m.css > production.css $JAVA -jar $YUI_LOCATION production.css -o production.min.css cd ../../relay -cat ../dialog/resources/jschannel.js relay.js > production.js +cat ../dialog/resources/jschannel.js ../dialog/resources/browserid.js relay.js > production.js $UGLIFY < production.js > production.min.js mv production.min.js production.js @@ -48,7 +50,7 @@ echo '' cd ../js # re-minimize everything together -cat jquery-1.6.2.min.js json2.js browserid.js ../dialog/resources/underscore-min.js ../dialog/resources/browserid-extensions.js ../dialog/resources/storage.js ../dialog/resources/network.js ../dialog/resources/user.js ../dialog/resources/tooltip.js ../dialog/resources/validation.js pages/index.js pages/add_email_address.js pages/verify_email_address.js pages/manage_account.js pages/signin.js pages/signup.js pages/forgot.js > lib.js +cat jquery-1.6.2.min.js json2.js ../dialog/resources/browserid.js page_helpers.js browserid.js ../dialog/resources/underscore-min.js ../dialog/resources/browserid-extensions.js ../dialog/resources/storage.js ../dialog/resources/network.js ../dialog/resources/user.js ../dialog/resources/tooltip.js ../dialog/resources/validation.js pages/index.js pages/add_email_address.js pages/verify_email_address.js pages/manage_account.js pages/signin.js pages/signup.js pages/forgot.js > lib.js $UGLIFY < lib.js > lib.min.js cd ../css diff --git a/scripts/rpmbuild.sh b/scripts/rpmbuild.sh new file mode 100755 index 0000000000000000000000000000000000000000..46f30b1eeea913a6d94135524d19fbccd6e7db77 --- /dev/null +++ b/scripts/rpmbuild.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +set -e + +progname=$(basename $0) + +cd $(dirname $0)/.. # top level of the checkout + +curdir=$(basename $PWD) +if [ "$curdir" != "browserid" ]; then + echo "$progname: git checkout must be in a dir named 'browserid'" >&2 + exit 1 +fi + +mkdir -p rpmbuild/SOURCES rpmbuild/SPECS +rm -rf rpmbuild/RPMS + +tar -C .. --exclude rpmbuild -czf \ + $PWD/rpmbuild/SOURCES/browserid-server.tar.gz browserid + +set +e + +rpmbuild --define "_topdir $PWD/rpmbuild" -ba scripts/browserid.spec +rc=$? +if [ $rc -eq 0 ]; then + ls -l $PWD/rpmbuild/RPMS/*/*.rpm +else + echo "$progname: failed to build browserid RPM (rpmbuild rc=$rc)" >&2 +fi + +exit $rc diff --git a/scripts/run_all_tests.sh b/scripts/run_all_tests.sh index ed6ab62f2f1afaa86b8cfe1974258dddb0e0d0bc..3976cd66fab79b019c05aa4c05f6cbfee37cccb8 100755 --- a/scripts/run_all_tests.sh +++ b/scripts/run_all_tests.sh @@ -16,9 +16,9 @@ cd $BASEDIR for env in test_json test_mysql ; do export NODE_ENV=$env $SCRIPT_DIR/test_db_connectivity.js - if [ $? = 0 ] ; then + if [ $? = 0 ] ; then echo "Testing with NODE_ENV=$env" - for file in browserid/tests/*.js ; do + for file in tests/*.js ; do echo $file vows $file if [[ $? != 0 ]] ; then diff --git a/scripts/run_locally.js b/scripts/run_locally.js new file mode 100755 index 0000000000000000000000000000000000000000..25ded3dcce690b0fae6302c17b56af024eb81917 --- /dev/null +++ b/scripts/run_locally.js @@ -0,0 +1,76 @@ +#!/usr/bin/env node + +const +spawn = require('child_process').spawn, +path = require('path'); + +exports.daemons = daemons = {}; + +const HOST = process.env['IP_ADDRESS'] || process.env['HOST'] || "127.0.0.1"; + +var daemonsToRun = { + verifier: { + PORT: 10000, + HOST: HOST + }, + keysigner: { + PORT: 10003, + HOST: HOST + }, + example: { + path: path.join(__dirname, "..", "scripts", "serve_example.js"), + PORT: 10001, + HOST: HOST + }, + browserid: { + PORT: 10002, + HOST: HOST + } +}; + +// all spawned processes should log to console +process.env['LOG_TO_CONSOLE'] = 1; + +// all spawned processes will communicate with the local browserid +process.env['BROWSERID_URL'] = 'http://' + HOST + ":10002"; +process.env['VERIFIER_URL'] = 'http://' + HOST + ":10000/verify"; +process.env['KEYSIGNER_URL'] = 'http://' + HOST + ":10003"; + +Object.keys(daemonsToRun).forEach(function(k) { + Object.keys(daemonsToRun[k]).forEach(function(ek) { + process.env[ek] = daemonsToRun[k][ek]; + }); + var pathToScript = daemonsToRun[k].path || path.join(__dirname, "..", "bin", k); + var p = spawn('node', [ pathToScript ]); + + function dump(d) { + d.toString().split('\n').forEach(function(d) { + if (d.length === 0) return; + console.log(k, '(' + p.pid + '):', d); + }); + } + + p.stdout.on('data', dump); + p.stderr.on('data', dump); + + console.log("spawned", k, "("+pathToScript+") with pid", p.pid); + Object.keys(daemonsToRun[k]).forEach(function(ek) { + delete process.env[ek]; + }); + + daemons[k] = p; + + p.on('exit', function (code, signal) { + console.log(k, 'exited with code', code, (signal ? 'on signal ' + signal : "")); + delete daemons[k]; + Object.keys(daemons).forEach(function (k) { daemons[k].kill(); }); + if (Object.keys(daemons).length === 0) { + console.log("all daemons torn down, exiting..."); + } + }); +}); + +process.on('SIGINT', function () { + console.log('\nSIGINT recieved! trying to shut down gracefully...'); + Object.keys(daemons).forEach(function (k) { daemons[k].kill('SIGINT'); }); +}); diff --git a/scripts/serve_example.js b/scripts/serve_example.js new file mode 100755 index 0000000000000000000000000000000000000000..904081830eca2f7392ede8c1d7434c488d402b01 --- /dev/null +++ b/scripts/serve_example.js @@ -0,0 +1,31 @@ +#!/usr/bin/env node + +// finally, let's run a tiny webserver for the example code. +const +express = require('express'), +path = require('path'), +urlparse = require('urlparse'), +postprocess = require('postprocess'); + +var exampleServer = express.createServer(); + +exampleServer.use(express.logger()); + +if (process.env['BROWSERID_URL']) { + var burl = urlparse(process.env['BROWSERID_URL']).validate().normalize().originOnly().toString(); + console.log('using browserid server at ' + burl); + + exampleServer.use(postprocess.middleware(function(req, buffer) { + return buffer.toString().replace(new RegExp('https://browserid.org', 'g'), burl); + })); +} + +exampleServer.use(express.static(path.join(__dirname, "..", "example"))); + +exampleServer.listen( + process.env['PORT'] || 10001, + process.env['HOST'] || process.env['IP_ADDRESS'] || "127.0.0.1", + function() { + var addy = exampleServer.address(); + console.log("http://" + addy.address + ":" + addy.port); + }); diff --git a/scripts/test_db_connectivity.js b/scripts/test_db_connectivity.js index 2a446931742ee6b531700c78839d835acaee104c..acdfdad023c69204e2a8838fc5e734d9c9e0427e 100755 --- a/scripts/test_db_connectivity.js +++ b/scripts/test_db_connectivity.js @@ -4,8 +4,8 @@ // the database using the present configuration. const -configuration = require('../libs/configuration.js'), -db = require('../browserid/lib/db.js'); +configuration = require('../lib/configuration.js'), +db = require('../lib/db.js'); var dbCfg = configuration.get('database'); diff --git a/test.sh b/test.sh deleted file mode 100755 index b41801ed1cbc80f3f397258f877b52aa5a4e0910..0000000000000000000000000000000000000000 --- a/test.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -npm test diff --git a/browserid/tests/ca-test.js b/tests/ca-test.js similarity index 97% rename from browserid/tests/ca-test.js rename to tests/ca-test.js index 2dfd984ee4120a048df26965b0b3537c76941316..c990798d0e1439fb3ba835171b27e6bafaac4742 100755 --- a/browserid/tests/ca-test.js +++ b/tests/ca-test.js @@ -41,8 +41,8 @@ const assert = require('assert'), vows = require('vows'), start_stop = require('./lib/start-stop.js'), wsapi = require('./lib/wsapi.js'), -email = require('../lib/email.js'), -ca = require('../lib/ca.js'), +email = require('browserid/email.js'), +ca = require('browserid/ca.js'), jwcert = require('jwcrypto/jwcert'), jwk = require('jwcrypto/jwk'), jws = require('jwcrypto/jws'); diff --git a/browserid/tests/cert-emails-test.js b/tests/cert-emails-test.js similarity index 90% rename from browserid/tests/cert-emails-test.js rename to tests/cert-emails-test.js index 2b3ef1cafe5b7229983a55dca63725dcf146f07c..b8e353fe3057b5829c60bfb901199f23ed9257a3 100755 --- a/browserid/tests/cert-emails-test.js +++ b/tests/cert-emails-test.js @@ -41,8 +41,8 @@ const assert = require('assert'), vows = require('vows'), start_stop = require('./lib/start-stop.js'), wsapi = require('./lib/wsapi.js'), -email = require('../lib/email.js'), -ca = require('../lib/ca.js'), +email = require('browserid/email.js'), +ca = require('browserid/ca.js'), jwcert = require('jwcrypto/jwcert'), jwk = require('jwcrypto/jwk'), jws = require('jwcrypto/jws'), @@ -58,21 +58,36 @@ start_stop.addStartupBatches(suite); // ever time a new token is sent out, let's update the global // var 'token' var token = undefined; -email.setInterceptor(function(email, site, secret) { token = secret; }); +start_stop.browserid.on('token', function(secret) { + token = secret; +}); // INFO: some of these tests are repeat of sync-emails... to set // things up properly for key certification // create a new account via the api with (first address) suite.addBatch({ - "stage an account": { + "staging an account": { topic: wsapi.post('/wsapi/stage_user', { email: 'syncer@somehost.com', pubkey: 'fakekey', site:'fakesite.com' }), - "yields a sane token": function(r, err) { - assert.strictEqual(typeof token, 'string'); + "succeeds": function(r, err) { + assert.strictEqual(r.code, 200); + } + } +}); + +// wait for the token +suite.addBatch({ + "a token": { + topic: function() { + if (token) return token; + else start_stop.browserid.once('token', this.callback); + }, + "is obtained": function (t) { + assert.strictEqual(typeof t, 'string'); } } }); @@ -112,13 +127,13 @@ suite.addBatch({ assert.strictEqual(r.code, 400); } }, - "cert key invoked with just an email": { + "cert key invoked with just an email": { topic: wsapi.post(cert_key_url, { email: 'syncer@somehost.com' }), "returns a 400" : function(r, err) { assert.strictEqual(r.code, 400); } }, - "cert key invoked with proper argument": { + "cert key invoked with proper argument": { topic: wsapi.post(cert_key_url, { email: 'syncer@somehost.com', pubkey: kp.publicKey.serialize() }), "returns a response with a proper content-type" : function(r, err) { assert.strictEqual(r.code, 200); diff --git a/browserid/tests/cookie-session-security-test.js b/tests/cookie-session-security-test.js similarity index 96% rename from browserid/tests/cookie-session-security-test.js rename to tests/cookie-session-security-test.js index 3a1d18817dd5a44f2d73c27099c7ffd088de1624..dad6e25452b47e29e050ecd97b2177153f5582c6 100755 --- a/browserid/tests/cookie-session-security-test.js +++ b/tests/cookie-session-security-test.js @@ -41,9 +41,9 @@ const assert = require('assert'), vows = require('vows'), start_stop = require('./lib/start-stop.js'), wsapi = require('./lib/wsapi.js'), -wcli = require('../../libs/wsapi_client'); -email = require('../lib/email.js'), -ca = require('../lib/ca.js'), +wcli = require('wsapi_client'); +email = require('browserid/email.js'), +ca = require('browserid/ca.js'), jwcert = require('jwcrypto/jwcert'), jwk = require('jwcrypto/jwk'), jws = require('jwcrypto/jws'); diff --git a/browserid/tests/db-test.js b/tests/db-test.js similarity index 98% rename from browserid/tests/db-test.js rename to tests/db-test.js index c4c1136bf02850e2ceab1989d8e198f9947973d1..6f7d92c5eabb7e658747d877ffc01e93dbcfaa3d 100755 --- a/browserid/tests/db-test.js +++ b/tests/db-test.js @@ -37,19 +37,22 @@ require('./lib/test_env.js'); +// add lib/ to the require path + const assert = require('assert'), vows = require('vows'), -db = require('../lib/db.js'), fs = require('fs'), path = require('path'), -configuration = require('../../libs/configuration.js'); +db = require('db.js'), +configuration = require('configuration.js'); var suite = vows.describe('db'); // disable vows (often flakey?) async error behavior suite.options.error = false; var dbCfg = configuration.get('database'); +dbCfg.drop_on_close = true; suite.addBatch({ "onReady": { @@ -192,7 +195,7 @@ suite.addBatch({ db.isStaged('lloyd@somewhe.re', function(r) { cb(secret, r); }); }, "makes it visible via isStaged": function(sekret, r) { assert.isTrue(r); }, - "and lets you verify it": { + "lets you verify it": { topic: function(secret, r) { db.gotVerificationSecret(secret, undefined, this.callback); }, diff --git a/tests/email-throttling-test.js b/tests/email-throttling-test.js new file mode 100755 index 0000000000000000000000000000000000000000..52e86a126631e8c13183ca86c2fd032ab7aa5707 --- /dev/null +++ b/tests/email-throttling-test.js @@ -0,0 +1,162 @@ +#!/usr/bin/env node + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla BrowserID. + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +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('email-throttling'); + +var token; + +// start up a pristine server +start_stop.addStartupBatches(suite); + +// now stage a registration (causing an email to be sent) +suite.addBatch({ + "staging a registration": { + topic: wsapi.post('/wsapi/stage_user', { + email: 'first@fakeemail.com', + site:'fakesite.com' + }), + "returns 200": function(r, err) { + assert.strictEqual(r.code, 200); + } + } +}); + +// wait for the token +suite.addBatch({ + "a token": { + topic: function() { + if (token) return token; + else start_stop.browserid.once('token', this.callback); + }, + "is obtained": function (t) { + assert.strictEqual(typeof t, 'string'); + token = t; + } + } +}); + +suite.addBatch({ + "immediately staging another": { + topic: wsapi.post('/wsapi/stage_user', { + email: 'first@fakeemail.com', + site:'fakesite.com' + }), + "is throttled": function(r, err) { + assert.strictEqual(r.code, 403); + } + } +}); + +suite.addBatch({ + "finishing creating the first account": { + topic: function() { + wsapi.post('/wsapi/complete_user_creation', { token: token, pass: 'firstfakepass' }).call(this); + }, + "works": function(r, err) { + assert.equal(r.code, 200); + assert.strictEqual(true, JSON.parse(r.body).success); + token = undefined; + } + } +}); + +suite.addBatch({ + "add a new email address to our account": { + topic: wsapi.post('/wsapi/stage_email', { + email: 'second@fakeemail.com', + site:'fakesite.com' + }), + "works": function(r, err) { + assert.strictEqual(r.code, 200); + } + } +}); + +// wait for the token +suite.addBatch({ + "a token": { + topic: function() { + if (token) return token; + else start_stop.browserid.once('token', this.callback); + }, + "is obtained": function (t) { + assert.strictEqual(typeof t, 'string'); + token = t; + } + } +}); + +suite.addBatch({ + "re-adding that same new email address a second time": { + topic: wsapi.post('/wsapi/stage_email', { + email: 'second@fakeemail.com', + site:'fakesite.com' + }), + "is throttled with a 403": function(r, err) { + assert.strictEqual(r.code, 403); + } + } +}); + +suite.addBatch({ + "and when we attempt to finish adding the email address": { + topic: function() { + wsapi.post('/wsapi/complete_email_addition', { token: token }).call(this); + }, + "it works swimmingly": function(r, err) { + assert.equal(r.code, 200); + assert.strictEqual(JSON.parse(r.body).success, true); + token = undefined; + } + } +}); + + +// shut the server down and cleanup +start_stop.addShutdownBatches(suite); + +// run or export the suite. +if (process.argv[1] === __filename) suite.run(); +else suite.export(module); diff --git a/browserid/tests/forgotten-email-test.js b/tests/forgotten-email-test.js similarity index 84% rename from browserid/tests/forgotten-email-test.js rename to tests/forgotten-email-test.js index 8fa595b469eec88174cfe2b9c08150dc8fcac0d5..87774f4a812059525096d799d5b843976cf491de 100755 --- a/browserid/tests/forgotten-email-test.js +++ b/tests/forgotten-email-test.js @@ -41,29 +41,39 @@ const assert = require('assert'), vows = require('vows'), start_stop = require('./lib/start-stop.js'), wsapi = require('./lib/wsapi.js'), -email = require('../lib/email.js'); +email = require('browserid/email.js'); var suite = vows.describe('forgotten-email'); -// disable vows (often flakey?) async error behavior -suite.options.error = false; - start_stop.addStartupBatches(suite); -// ever time a new token is sent out, let's update the global +// every time a new token is sent out, let's update the global // var 'token' var token = undefined; -email.setInterceptor(function(email, site, secret) { token = secret; }); // create a new account via the api with (first address) suite.addBatch({ - "stage first account": { + "staging an account": { topic: wsapi.post('/wsapi/stage_user', { email: 'first@fakeemail.com', site:'fakesite.com' }), - "the token is sane": function(r, err) { - assert.strictEqual('string', typeof token); + "works": function(r, err) { + assert.strictEqual(r.code, 200); + } + } +}); + +// wait for the token +suite.addBatch({ + "a token": { + topic: function() { + if (token) return token; + else start_stop.browserid.once('token', this.callback); + }, + "is obtained": function (t) { + assert.strictEqual(typeof t, 'string'); + token = t; } } }); @@ -76,6 +86,7 @@ suite.addBatch({ "account created": function(r, err) { assert.equal(r.code, 200); assert.strictEqual(true, JSON.parse(r.body).success); + token = undefined; } } }); @@ -97,8 +108,22 @@ suite.addBatch({ email: 'second@fakeemail.com', site:'fakesite.com' }), - "the token is sane": function(r, err) { - assert.strictEqual('string', typeof token); + "works": function(r, err) { + assert.strictEqual(r.code, 200); + } + } +}); + +// wait for the token +suite.addBatch({ + "a token": { + topic: function() { + if (token) return token; + else start_stop.browserid.once('token', this.callback); + }, + "is obtained": function (t) { + assert.strictEqual(typeof t, 'string'); + token = t; } } }); @@ -112,6 +137,7 @@ suite.addBatch({ "account created": function(r, err) { assert.equal(r.code, 200); assert.strictEqual(JSON.parse(r.body).success, true); + token = undefined; } } }); @@ -146,8 +172,22 @@ suite.addBatch({ email: 'first@fakeemail.com', site:'otherfakesite.com' }), - "the token is sane": function(r, err) { - assert.strictEqual('string', typeof token); + "works": function(r, err) { + assert.strictEqual(r.code, 200); + } + } +}); + +// wait for the token +suite.addBatch({ + "a token": { + topic: function() { + if (token) return token; + else start_stop.browserid.once('token', this.callback); + }, + "is obtained": function (t) { + assert.strictEqual(typeof t, 'string'); + token = t; } } }); diff --git a/tests/lib/start-stop.js b/tests/lib/start-stop.js new file mode 100644 index 0000000000000000000000000000000000000000..67b49c9dcb36a32e7d71b05e2bd90a41563afcdc --- /dev/null +++ b/tests/lib/start-stop.js @@ -0,0 +1,198 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla BrowserID. + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +const +assert = require('assert'), +fs = require('fs'), +path = require('path'), +wsapi = require('./wsapi.js'), +spawn = require('child_process').spawn, +events = require('events'), +config = require('configuration'), +db = require('db'); + +var proc = undefined; + +process.on('exit', function () { + if (proc) { proc.kill(); } +}); + +exports.browserid = new events.EventEmitter; + +function setupProc(proc) { + var m, sentReady = false; + + proc.stdout.on('data', function(x) { +// console.log(x.toString()); + var tokenRegex = new RegExp('token=([A-Za-z0-9]+)$', 'm'); + + if (!sentReady && /^browserid.*127\.0\.0\.1:10002/.test(x)) { + exports.browserid.emit('ready'); + sentReady = true; + } else if (m = tokenRegex.exec(x)) { + exports.browserid.emit('token', m[1]); + } + }); +} + +function removeVarDir() { + try { + fs.readdirSync(varPath).forEach(function(f) { + fs.unlinkSync(path.join(varPath, f)); + }); + fs.rmdirSync(varPath); + } catch(e) {} +} + +exports.addStartupBatches = function(suite) { + + // disable vows (often flakey?) async error behavior + suite.options.error = false; + + // propogate our ephemeral database parameters down to + // child processes so that all process are communicating + // with the same db + suite.addBatch({ + "specifying an ephemeral database": { + topic: function() { + if (config.get('database').driver === 'mysql') { + process.env['MYSQL_DATABASE_NAME'] = config.get('database').database; + } else if (config.get('database').driver === 'json') { + process.env['JSON_DATABASE_PATH'] = config.get('database').path; + } + return true; + }, + "should work": function(x) { + var cfg = process.env['MYSQL_DATABASE_NAME'] || process.env['JSON_DATABASE_PATH']; + assert.equal(typeof cfg, 'string'); + } + } + }); + + suite.addBatch({ + "opening the database": { + topic: function() { + var cfg = config.get('database'); + cfg.drop_on_close = true; + db.open(cfg, this.callback); + }, + "should work fine": function(r) { + assert.isUndefined(r); + } + } + }); + + suite.addBatch({ + "run the server": { + topic: function() { + var pathToHarness = path.join(__dirname, '..', '..', 'scripts', 'run_locally.js'); + proc = spawn('node', [ pathToHarness ]) + setupProc(proc); + exports.browserid.on('ready', this.callback); + }, + "server should be running": { + topic: wsapi.get('/__heartbeat__'), + "server is running": function (r, err) { + assert.equal(r.code, 200); + assert.equal(r.body, 'ok'); + } + } + } + }); +}; + +exports.addRestartBatch = function(suite) { + // stop the server + suite.addBatch({ + "stop the server": { + topic: function() { + var cb = this.callback; + proc.kill('SIGINT'); + proc.on('exit', this.callback); + }, + "stopped": function(x) { + assert.strictEqual(x, 0); + } + } + }); + + suite.addBatch({ + "run the server": { + topic: function() { + var pathToHarness = path.join(__dirname, '..', '..', 'scripts', 'run_locally.js'); + proc = spawn('node', [ pathToHarness ]) + setupProc(proc); + exports.browserid.on('ready', this.callback); + }, + "server should be running": { + topic: wsapi.get('/__heartbeat__'), + "server is running": function (r, err) { + assert.equal(r.code, 200); + assert.equal(r.body, 'ok'); + } + } + } + }); + +}; + +exports.addShutdownBatches = function(suite) { + // stop the server + suite.addBatch({ + "stop the server": { + topic: function() { + var cb = this.callback; + proc.kill('SIGINT'); + proc.on('exit', this.callback); + }, + "stopped": function(x) { + assert.strictEqual(x, 0); + } + } + }); + + // clean up + suite.addBatch({ + "closing the database": { + topic: function() { + db.close(this.callback); + }, + "should work": function(err) { + assert.isUndefined(err); + } + } + }); +} + diff --git a/browserid/tests/lib/test_env.js b/tests/lib/test_env.js similarity index 96% rename from browserid/tests/lib/test_env.js rename to tests/lib/test_env.js index a98816c4ad723f88d2654b44b2e0acc972965bfc..9017cebc5152e551a731735391252584fe9379ce 100644 --- a/browserid/tests/lib/test_env.js +++ b/tests/lib/test_env.js @@ -44,4 +44,6 @@ if (undefined === process.env['NODE_ENV']) { process.env['NODE_ENV'] = 'test_json'; } else if (process.env['NODE_ENV'].substr(0,5) !== 'test_') { console.log("(Woah. Running tests without a test_ configuration. Is this *really* what you want?)"); -} +} + +require.paths.unshift(require('path').join(__dirname, '..', '..', 'lib')); diff --git a/browserid/tests/lib/wsapi.js b/tests/lib/wsapi.js similarity index 96% rename from browserid/tests/lib/wsapi.js rename to tests/lib/wsapi.js index a738c16cee4c71e8f68257444473d02a12affa82..d41d253142eda893ef146fc2019e67c24cbecf3b 100644 --- a/browserid/tests/lib/wsapi.js +++ b/tests/lib/wsapi.js @@ -34,14 +34,14 @@ * ***** END LICENSE BLOCK ***** */ const -wcli = require('../../../libs/wsapi_client'); +wcli = require('wsapi_client'); // the client "context" var context = {}; // the configuration var configuration = { - browserid: 'http://127.0.0.1:62700/' + browserid: 'http://127.0.0.1:10002/' } exports.clearCookies = function() { diff --git a/browserid/tests/list-emails-wsapi-test.js b/tests/list-emails-wsapi-test.js similarity index 89% rename from browserid/tests/list-emails-wsapi-test.js rename to tests/list-emails-wsapi-test.js index e31480e25f5d263331a555bb646ea5b1cd415f9d..1cc60423148f69d2cdaf8cc3b1d90fb26f80f5ac 100755 --- a/browserid/tests/list-emails-wsapi-test.js +++ b/tests/list-emails-wsapi-test.js @@ -40,8 +40,7 @@ require('./lib/test_env.js'); const assert = require('assert'), vows = require('vows'), start_stop = require('./lib/start-stop.js'), -wsapi = require('./lib/wsapi.js'), -email = require('../lib/email.js'); +wsapi = require('./lib/wsapi.js'); var suite = vows.describe('forgotten-email'); @@ -53,7 +52,6 @@ start_stop.addStartupBatches(suite); // ever time a new token is sent out, let's update the global // var 'token' var token = undefined; -email.setInterceptor(function(email, site, secret) { token = secret; }); // create a new account via the api with (first address) suite.addBatch({ @@ -62,8 +60,22 @@ suite.addBatch({ email: 'syncer@somehost.com', site:'fakesite.com' }), - "yields a sane token": function(r, err) { - assert.strictEqual(typeof token, 'string'); + "works": function(r, err) { + assert.strictEqual(r.code, 200); + } + } +}); + +// wait for the token +suite.addBatch({ + "a token": { + topic: function() { + if (token) return token; + else start_stop.browserid.once('token', this.callback); + }, + "is obtained": function (t) { + assert.strictEqual(typeof t, 'string'); + token = t; } } }); @@ -76,6 +88,7 @@ suite.addBatch({ "works": function(r, err) { assert.equal(r.code, 200); assert.strictEqual(JSON.parse(r.body).success, true); + token = undefined; } } }); diff --git a/tests/page-requests-test.js b/tests/page-requests-test.js new file mode 100755 index 0000000000000000000000000000000000000000..2a5b530e46fadb15eddb06682b1459ecddae8f20 --- /dev/null +++ b/tests/page-requests-test.js @@ -0,0 +1,115 @@ +#!/usr/bin/env node + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla BrowserID. + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +require('./lib/test_env.js'); + +const assert = require('assert'), +http = require('http'), +vows = require('vows'), +start_stop = require('./lib/start-stop.js'), +wsapi = require('./lib/wsapi.js'); + +var suite = vows.describe('page requests'); + +// start up a pristine server +start_stop.addStartupBatches(suite); + +// This set of tests check to make sure all of the expected pages are served +// up with the correct status codes. We use Lloyd's wsapi client as our REST +// interface. + + +// Taken from the vows page. +function assertStatus(code) { + return function (res, err) { + assert.equal(res.code, code); + }; +} + +function respondsWith(status) { + var context = { + topic: function () { + // Get the current context's name, such as "POST /" + // and split it at the space. + var req = this.context.name.split(/ +/), // ["POST", "/"] + method = req[0].toLowerCase(), // "post" + path = req[1]; // "/" + + // Perform the contextual client request, + // with the above method and path. + wsapi[method](path).call(this); + } + }; + + // Create and assign the vow to the context. + // The description is generated from the expected status code + // and the status name, from node's http module. + context['should respond with a ' + status + ' ' + + http.STATUS_CODES[status]] = assertStatus(status); + + return context; +} + +suite.addBatch({ + 'GET /': respondsWith(200), + 'GET /signup': respondsWith(200), + 'GET /forgot': respondsWith(200), + 'GET /signin': respondsWith(200), + 'GET /about': respondsWith(200), + 'GET /tos': respondsWith(200), + 'GET /privacy': respondsWith(200), + 'GET /verify_email_address': respondsWith(200), + 'GET /add_email_address': respondsWith(200), + 'GET /pk': respondsWith(200), + 'GET /vepbundle': respondsWith(200), + 'GET /signin': respondsWith(200), + 'GET /unsupported_dialog': respondsWith(200), + 'GET /developers': respondsWith(200), + 'GET /manage': respondsWith(302), + 'GET /users': respondsWith(302), + 'GET /users/': respondsWith(302), + 'GET /primaries': respondsWith(302), + 'GET /primaries/': respondsWith(302), + 'GET /developers': respondsWith(302) +}); + +// shut the server down and cleanup +start_stop.addShutdownBatches(suite); + +// run or export the suite. +if (process.argv[1] === __filename) suite.run(); +else suite.export(module); diff --git a/browserid/tests/password-bcrypt-update-test.js b/tests/password-bcrypt-update-test.js similarity index 92% rename from browserid/tests/password-bcrypt-update-test.js rename to tests/password-bcrypt-update-test.js index d5693eeae05b6bdd67cca8a11bd3aece966c2471..ad0483652b4db4414215e6a7489fe062f0a7d065 100755 --- a/browserid/tests/password-bcrypt-update-test.js +++ b/tests/password-bcrypt-update-test.js @@ -42,9 +42,8 @@ require('assert'), vows = require('vows'), start_stop = require('./lib/start-stop.js'), wsapi = require('./lib/wsapi.js'), -email = require('../lib/email.js'), -db = require('../lib/db.js'), -config = require('../../libs/configuration.js'), +db = require('db.js'), +config = require('configuration.js'), bcrypt = require('bcrypt'); var suite = vows.describe('password-length'); @@ -59,9 +58,6 @@ const TEST_EMAIL = 'update@passwd.bcrypt', // surpress console output of emails with a noop email interceptor var token = undefined; -email.setInterceptor(function(email, site, secret) { - token = secret; -}); suite.addBatch({ "get csrf token": { @@ -89,6 +85,20 @@ suite.addBatch({ } }); +// wait for the token +suite.addBatch({ + "a token": { + topic: function() { + if (token) return token; + else start_stop.browserid.once('token', this.callback); + }, + "is obtained": function (t) { + assert.strictEqual(typeof t, 'string'); + token = t; + } + } +}); + // create a new account via the api with (first address) suite.addBatch({ "setting password": { @@ -121,13 +131,15 @@ suite.addBatch({ suite.addBatch({ "updating work factor": { topic: function() { - config.set('bcrypt_work_factor', 8); + process.env['BCRYPT_WORK_FACTOR'] = 8; return true; }, "succeeds": function() {} } }); +start_stop.addRestartBatch(suite); + // at authentication time we should see the password get updated suite.addBatch({ "re-authentication": { diff --git a/browserid/tests/password-length-test.js b/tests/password-length-test.js similarity index 97% rename from browserid/tests/password-length-test.js rename to tests/password-length-test.js index 6bd243cc478132e9c68231299a32a74f4b0715b7..055afd202e0f66e00acc59d5f5f5c465a6173dff 100755 --- a/browserid/tests/password-length-test.js +++ b/tests/password-length-test.js @@ -42,7 +42,7 @@ require('assert'), vows = require('vows'), start_stop = require('./lib/start-stop.js'), wsapi = require('./lib/wsapi.js'), -email = require('../lib/email.js'); +email = require('browserid/email.js'); var suite = vows.describe('password-length'); @@ -51,9 +51,11 @@ suite.options.error = false; start_stop.addStartupBatches(suite); -// surpress console output of emails with a noop email interceptor +// surpress console output of emails with a noop email intercepto var token = undefined; -email.setInterceptor(function(email, site, secret) { token = secret; }); +start_stop.browserid.on('token', function(secret) { + token = secret; +}); suite.addBatch({ "get csrf token": { @@ -79,7 +81,7 @@ suite.addBatch({ assert.equal(r.code, 200); } } -}) +}); // create a new account via the api with (first address) suite.addBatch({ @@ -113,7 +115,6 @@ suite.addBatch({ } } }); - start_stop.addShutdownBatches(suite); // run or export the suite. diff --git a/browserid/tests/registration-status-wsapi-test.js b/tests/registration-status-wsapi-test.js similarity index 90% rename from browserid/tests/registration-status-wsapi-test.js rename to tests/registration-status-wsapi-test.js index b4bc4be330d65e913d657740e413bd939152cdb1..e0d40aa7762a1062482d8954cd200bd3fa03e6b4 100755 --- a/browserid/tests/registration-status-wsapi-test.js +++ b/tests/registration-status-wsapi-test.js @@ -41,8 +41,7 @@ const assert = require('assert'), vows = require('vows'), start_stop = require('./lib/start-stop.js'), -wsapi = require('./lib/wsapi.js'), -email = require('../lib/email.js'); +wsapi = require('./lib/wsapi.js'); var suite = vows.describe('registration-status-wsapi'); @@ -52,7 +51,6 @@ var suite = vows.describe('registration-status-wsapi'); // ever time a new token is sent out, let's update the global // var 'token' var token = undefined; -email.setInterceptor(function(email, site, secret) {token = secret; }); // start up a pristine server start_stop.addStartupBatches(suite); @@ -82,10 +80,25 @@ suite.addBatch({ email: 'first@fakeemail.com', site:'fakesite.com' }), - "the token is sane": function(r, err) { - assert.strictEqual('string', typeof token); + "returns 200": function(r, err) { + assert.strictEqual(r.code, 200); + } + } +}); + +// wait for the token +suite.addBatch({ + "a token": { + topic: function() { + if (token) return token; + else start_stop.browserid.once('token', this.callback); + }, + "is obtained": function (t) { + assert.strictEqual(typeof t, 'string'); + token = t; } - }}); + } +}); suite.addBatch({ "comparing token to email": { @@ -129,6 +142,7 @@ suite.addBatch({ }, "works": function(r, err) { assert.equal(r.code, 200); + token = undefined; } } }); @@ -186,8 +200,22 @@ suite.addBatch({ email: 'first@fakeemail.com', site:'secondfakesite.com' }), - "yields a valid token": function(r, err) { - assert.strictEqual('string', typeof token); + "yields a HTTP 200": function (r, err) { + assert.strictEqual(r.code, 200); + } + } +}); + +// wait for the token +suite.addBatch({ + "a token": { + topic: function() { + if (token) return token; + else start_stop.browserid.once('token', this.callback); + }, + "is obtained": function (t) { + assert.strictEqual(typeof t, 'string'); + token = t; } } }); @@ -211,6 +239,7 @@ suite.addBatch({ }, "and returns a 200 code": function(r, err) { assert.equal(r.code, 200); + token = undefined; } } });